aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--INSTALL16
-rw-r--r--Makefile.am1279
-rw-r--r--NEWS22
-rw-r--r--configure.ac219
-rw-r--r--doc/user.xml207
-rw-r--r--m4/ax_append_link_flags.m461
-rw-r--r--m4/ax_check_link_flag.m471
-rw-r--r--m4/ax_cxx_compile_stdcxx_0x.m4107
-rw-r--r--m4/faad.m4128
-rwxr-xr-xscripts/makedist.sh25
-rwxr-xr-xscripts/mpd-indent.sh6
-rwxr-xr-xscripts/test.sh93
-rw-r--r--src/AllCommands.cxx385
-rw-r--r--src/AllCommands.hxx34
-rw-r--r--src/ApeLoader.cxx115
-rw-r--r--src/ApeLoader.hxx43
-rw-r--r--src/ApeReplayGain.cxx78
-rw-r--r--src/ApeReplayGain.hxx30
-rw-r--r--src/ApeTag.cxx104
-rw-r--r--src/ApeTag.hxx38
-rw-r--r--src/ArchiveFile.hxx56
-rw-r--r--src/ArchiveList.cxx90
-rw-r--r--src/ArchiveList.hxx47
-rw-r--r--src/ArchiveLookup.cxx111
-rw-r--r--src/ArchiveLookup.hxx32
-rw-r--r--src/ArchivePlugin.cxx43
-rw-r--r--src/ArchivePlugin.hxx65
-rw-r--r--src/ArchiveVisitor.hxx28
-rw-r--r--src/AudioCompress/compress.h9
-rw-r--r--src/AudioConfig.cxx51
-rw-r--r--src/AudioConfig.hxx31
-rw-r--r--src/AudioFormat.cxx85
-rw-r--r--src/AudioFormat.hxx315
-rw-r--r--src/AudioParser.cxx223
-rw-r--r--src/AudioParser.hxx47
-rw-r--r--src/CheckAudioFormat.cxx75
-rw-r--r--src/CheckAudioFormat.hxx54
-rw-r--r--src/Client.cxx36
-rw-r--r--src/Client.hxx79
-rw-r--r--src/ClientEvent.cxx36
-rw-r--r--src/ClientExpire.cxx42
-rw-r--r--src/ClientFile.cxx73
-rw-r--r--src/ClientFile.hxx42
-rw-r--r--src/ClientGlobal.cxx48
-rw-r--r--src/ClientIdle.cxx75
-rw-r--r--src/ClientInternal.hxx133
-rw-r--r--src/ClientList.cxx56
-rw-r--r--src/ClientList.hxx61
-rw-r--r--src/ClientMessage.cxx41
-rw-r--r--src/ClientMessage.hxx52
-rw-r--r--src/ClientNew.cxx127
-rw-r--r--src/ClientProcess.cxx135
-rw-r--r--src/ClientRead.cxx66
-rw-r--r--src/ClientSubscribe.cxx92
-rw-r--r--src/ClientSubscribe.hxx54
-rw-r--r--src/ClientWrite.cxx87
-rw-r--r--src/CommandError.cxx134
-rw-r--r--src/CommandError.hxx38
-rw-r--r--src/CommandLine.cxx248
-rw-r--r--src/CommandLine.hxx36
-rw-r--r--src/CommandListBuilder.cxx43
-rw-r--r--src/CommandListBuilder.hxx109
-rw-r--r--src/ConfigData.cxx133
-rw-r--r--src/ConfigData.hxx127
-rw-r--r--src/ConfigFile.cxx285
-rw-r--r--src/ConfigFile.hxx31
-rw-r--r--src/ConfigGlobal.cxx172
-rw-r--r--src/ConfigGlobal.hxx109
-rw-r--r--src/ConfigOption.hxx90
-rw-r--r--src/ConfigParser.cxx40
-rw-r--r--src/ConfigParser.hxx26
-rw-r--r--src/ConfigPath.cxx120
-rw-r--r--src/ConfigPath.hxx28
-rw-r--r--src/ConfigQuark.hxx36
-rw-r--r--src/ConfigTemplates.cxx96
-rw-r--r--src/ConfigTemplates.hxx33
-rw-r--r--src/CrossFade.cxx138
-rw-r--r--src/CrossFade.hxx50
-rw-r--r--src/DatabaseCommands.cxx220
-rw-r--r--src/DatabaseCommands.hxx57
-rw-r--r--src/DatabaseGlue.cxx167
-rw-r--r--src/DatabaseGlue.hxx59
-rw-r--r--src/DatabaseHelpers.cxx134
-rw-r--r--src/DatabaseHelpers.hxx41
-rw-r--r--src/DatabaseLock.cxx28
-rw-r--r--src/DatabaseLock.hxx99
-rw-r--r--src/DatabasePlaylist.cxx50
-rw-r--r--src/DatabasePlaylist.hxx34
-rw-r--r--src/DatabasePlugin.hxx146
-rw-r--r--src/DatabasePrint.cxx227
-rw-r--r--src/DatabasePrint.hxx57
-rw-r--r--src/DatabaseQueue.cxx54
-rw-r--r--src/DatabaseQueue.hxx32
-rw-r--r--src/DatabaseRegistry.cxx43
-rw-r--r--src/DatabaseRegistry.hxx37
-rw-r--r--src/DatabaseSave.cxx173
-rw-r--r--src/DatabaseSave.hxx36
-rw-r--r--src/DatabaseSelection.cxx27
-rw-r--r--src/DatabaseSelection.hxx56
-rw-r--r--src/DatabaseSimple.hxx86
-rw-r--r--src/DatabaseVisitor.hxx38
-rw-r--r--src/DecoderAPI.cxx561
-rw-r--r--src/DecoderAPI.hxx168
-rw-r--r--src/DecoderBuffer.cxx168
-rw-r--r--src/DecoderBuffer.hxx105
-rw-r--r--src/DecoderCommand.hxx30
-rw-r--r--src/DecoderControl.cxx193
-rw-r--r--src/DecoderControl.hxx299
-rw-r--r--src/DecoderError.hxx35
-rw-r--r--src/DecoderInternal.cxx106
-rw-r--r--src/DecoderInternal.hxx114
-rw-r--r--src/DecoderList.cxx240
-rw-r--r--src/DecoderList.hxx63
-rw-r--r--src/DecoderPlugin.cxx47
-rw-r--r--src/DecoderPlugin.hxx212
-rw-r--r--src/DecoderPrint.cxx53
-rw-r--r--src/DecoderPrint.hxx28
-rw-r--r--src/DecoderThread.cxx506
-rw-r--r--src/DecoderThread.hxx28
-rw-r--r--src/DespotifyUtils.cxx144
-rw-r--r--src/DespotifyUtils.hxx69
-rw-r--r--src/Directory.cxx335
-rw-r--r--src/Directory.hxx263
-rw-r--r--src/DirectorySave.cxx180
-rw-r--r--src/DirectorySave.hxx36
-rw-r--r--src/EncoderAPI.hxx33
-rw-r--r--src/EncoderList.cxx64
-rw-r--r--src/EncoderList.hxx43
-rw-r--r--src/EncoderPlugin.hxx328
-rw-r--r--src/ExcludeList.cxx77
-rw-r--r--src/ExcludeList.hxx80
-rw-r--r--src/FilterConfig.cxx120
-rw-r--r--src/FilterConfig.hxx44
-rw-r--r--src/FilterInternal.hxx71
-rw-r--r--src/FilterPlugin.cxx61
-rw-r--r--src/FilterPlugin.hxx70
-rw-r--r--src/FilterRegistry.cxx44
-rw-r--r--src/FilterRegistry.hxx40
-rw-r--r--src/GlobalEvents.cxx91
-rw-r--r--src/GlobalEvents.hxx72
-rw-r--r--src/IOThread.cxx167
-rw-r--r--src/IOThread.hxx71
-rw-r--r--src/IcyMetaDataParser.cxx171
-rw-r--r--src/IcyMetaDataParser.hxx85
-rw-r--r--src/IcyMetaDataServer.cxx139
-rw-r--r--src/IcyMetaDataServer.hxx36
-rw-r--r--src/IdTable.hxx91
-rw-r--r--src/Idle.cxx71
-rw-r--r--src/Idle.hxx83
-rw-r--r--src/InotifyQueue.cxx94
-rw-r--r--src/InotifyQueue.hxx41
-rw-r--r--src/InotifySource.cxx141
-rw-r--r--src/InotifySource.hxx72
-rw-r--r--src/InotifyUpdate.cxx363
-rw-r--r--src/InotifyUpdate.hxx47
-rw-r--r--src/InputInit.cxx107
-rw-r--r--src/InputInit.hxx39
-rw-r--r--src/InputInternal.cxx39
-rw-r--r--src/InputInternal.hxx33
-rw-r--r--src/InputPlugin.hxx88
-rw-r--r--src/InputRegistry.cxx73
-rw-r--r--src/InputRegistry.hxx43
-rw-r--r--src/InputStream.cxx256
-rw-r--r--src/InputStream.hxx114
-rw-r--r--src/Instance.cxx48
-rw-r--r--src/Instance.hxx54
-rw-r--r--src/Listen.cxx163
-rw-r--r--src/Listen.hxx32
-rw-r--r--src/Log.cxx339
-rw-r--r--src/Log.hxx53
-rw-r--r--src/Main.cxx597
-rw-r--r--src/Main.hxx75
-rw-r--r--src/Mapper.cxx302
-rw-r--r--src/Mapper.hxx146
-rw-r--r--src/MessageCommands.cxx137
-rw-r--r--src/MessageCommands.hxx42
-rw-r--r--src/MixerAll.cxx177
-rw-r--r--src/MixerAll.hxx60
-rw-r--r--src/MixerControl.cxx170
-rw-r--r--src/MixerControl.hxx69
-rw-r--r--src/MixerInternal.hxx59
-rw-r--r--src/MixerList.hxx35
-rw-r--r--src/MixerPlugin.hxx96
-rw-r--r--src/MixerType.cxx39
-rw-r--r--src/MixerType.hxx47
-rw-r--r--src/MusicBuffer.cxx79
-rw-r--r--src/MusicBuffer.hxx67
-rw-r--r--src/MusicChunk.cxx82
-rw-r--r--src/MusicChunk.hxx154
-rw-r--r--src/MusicPipe.cxx178
-rw-r--r--src/MusicPipe.hxx112
-rw-r--r--src/OtherCommands.cxx315
-rw-r--r--src/OtherCommands.hxx69
-rw-r--r--src/OutputAPI.hxx29
-rw-r--r--src/OutputAll.cxx595
-rw-r--r--src/OutputAll.hxx169
-rw-r--r--src/OutputCommand.cxx86
-rw-r--r--src/OutputCommand.hxx44
-rw-r--r--src/OutputCommands.cxx74
-rw-r--r--src/OutputCommands.hxx36
-rw-r--r--src/OutputControl.cxx333
-rw-r--r--src/OutputControl.hxx91
-rw-r--r--src/OutputError.hxx35
-rw-r--r--src/OutputFinish.cxx51
-rw-r--r--src/OutputInit.cxx331
-rw-r--r--src/OutputInternal.hxx279
-rw-r--r--src/OutputList.cxx100
-rw-r--r--src/OutputList.hxx33
-rw-r--r--src/OutputPlugin.cxx109
-rw-r--r--src/OutputPlugin.hxx210
-rw-r--r--src/OutputPrint.cxx45
-rw-r--r--src/OutputPrint.hxx33
-rw-r--r--src/OutputState.cxx91
-rw-r--r--src/OutputState.hxx44
-rw-r--r--src/OutputThread.cxx681
-rw-r--r--src/OutputThread.hxx27
-rw-r--r--src/Page.cxx70
-rw-r--r--src/Page.hxx104
-rw-r--r--src/Partition.hxx170
-rw-r--r--src/Permission.cxx127
-rw-r--r--src/Permission.hxx36
-rw-r--r--src/PlayerCommands.cxx396
-rw-r--r--src/PlayerCommands.hxx90
-rw-r--r--src/PlayerControl.cxx321
-rw-r--r--src/PlayerControl.hxx326
-rw-r--r--src/PlayerThread.cxx1212
-rw-r--r--src/PlayerThread.hxx45
-rw-r--r--src/Playlist.cxx343
-rw-r--r--src/Playlist.hxx255
-rw-r--r--src/PlaylistAny.cxx71
-rw-r--r--src/PlaylistAny.hxx42
-rw-r--r--src/PlaylistCommands.cxx221
-rw-r--r--src/PlaylistCommands.hxx60
-rw-r--r--src/PlaylistControl.cxx264
-rw-r--r--src/PlaylistDatabase.cxx77
-rw-r--r--src/PlaylistDatabase.hxx40
-rw-r--r--src/PlaylistEdit.cxx424
-rw-r--r--src/PlaylistFile.cxx466
-rw-r--r--src/PlaylistFile.hxx85
-rw-r--r--src/PlaylistGlobal.cxx49
-rw-r--r--src/PlaylistGlobal.hxx26
-rw-r--r--src/PlaylistInfo.hxx63
-rw-r--r--src/PlaylistMapper.cxx105
-rw-r--r--src/PlaylistMapper.hxx40
-rw-r--r--src/PlaylistPlugin.hxx142
-rw-r--r--src/PlaylistPrint.cxx190
-rw-r--r--src/PlaylistPrint.hxx110
-rw-r--r--src/PlaylistQueue.cxx89
-rw-r--r--src/PlaylistQueue.hxx59
-rw-r--r--src/PlaylistRegistry.cxx351
-rw-r--r--src/PlaylistRegistry.hxx84
-rw-r--r--src/PlaylistSave.cxx135
-rw-r--r--src/PlaylistSave.hxx60
-rw-r--r--src/PlaylistSong.cxx174
-rw-r--r--src/PlaylistSong.hxx37
-rw-r--r--src/PlaylistState.cxx229
-rw-r--r--src/PlaylistState.hxx52
-rw-r--r--src/PlaylistVector.cxx68
-rw-r--r--src/PlaylistVector.hxx56
-rw-r--r--src/Queue.cxx499
-rw-r--r--src/Queue.hxx371
-rw-r--r--src/QueueCommands.cxx387
-rw-r--r--src/QueueCommands.hxx84
-rw-r--r--src/QueuePrint.cxx107
-rw-r--r--src/QueuePrint.hxx54
-rw-r--r--src/QueueSave.cxx127
-rw-r--r--src/QueueSave.hxx42
-rw-r--r--src/ReplayGainConfig.cxx144
-rw-r--r--src/ReplayGainInfo.cxx48
-rw-r--r--src/SignalHandlers.cxx81
-rw-r--r--src/SignalHandlers.hxx25
-rw-r--r--src/SocketError.hxx156
-rw-r--r--src/SocketUtil.cxx95
-rw-r--r--src/SocketUtil.hxx58
-rw-r--r--src/Song.cxx175
-rw-r--r--src/Song.hxx147
-rw-r--r--src/SongFilter.cxx167
-rw-r--r--src/SongFilter.hxx107
-rw-r--r--src/SongPointer.hxx63
-rw-r--r--src/SongPrint.cxx74
-rw-r--r--src/SongPrint.hxx32
-rw-r--r--src/SongSave.cxx133
-rw-r--r--src/SongSave.hxx48
-rw-r--r--src/SongSort.cxx124
-rw-r--r--src/SongSort.hxx28
-rw-r--r--src/SongSticker.cxx150
-rw-r--r--src/SongSticker.hxx83
-rw-r--r--src/SongUpdate.cxx188
-rw-r--r--src/StateFile.cxx123
-rw-r--r--src/StateFile.hxx73
-rw-r--r--src/Stats.cxx89
-rw-r--r--src/StickerCommands.cxx176
-rw-r--r--src/StickerCommands.hxx30
-rw-r--r--src/StickerDatabase.cxx631
-rw-r--r--src/StickerDatabase.hxx157
-rw-r--r--src/StickerPrint.cxx44
-rw-r--r--src/StickerPrint.hxx38
-rw-r--r--src/Tag.cxx508
-rw-r--r--src/Tag.hxx223
-rw-r--r--src/TagFile.cxx84
-rw-r--r--src/TagFile.hxx35
-rw-r--r--src/TagHandler.cxx62
-rw-r--r--src/TagHandler.hxx101
-rw-r--r--src/TagId3.cxx587
-rw-r--r--src/TagId3.hxx70
-rw-r--r--src/TagInternal.hxx27
-rw-r--r--src/TagNames.c44
-rw-r--r--src/TagPool.cxx155
-rw-r--r--src/TagPool.hxx39
-rw-r--r--src/TagPrint.cxx48
-rw-r--r--src/TagPrint.hxx31
-rw-r--r--src/TagRva2.cxx150
-rw-r--r--src/TagRva2.hxx37
-rw-r--r--src/TagSave.cxx39
-rw-r--r--src/TagSave.hxx30
-rw-r--r--src/TagTable.hxx68
-rw-r--r--src/TagType.h56
-rw-r--r--src/TextFile.cxx61
-rw-r--r--src/TextFile.hxx67
-rw-r--r--src/TextInputStream.cxx92
-rw-r--r--src/TextInputStream.hxx59
-rw-r--r--src/TimePrint.cxx47
-rw-r--r--src/TimePrint.hxx33
-rw-r--r--src/Timer.cxx80
-rw-r--r--src/Timer.hxx49
-rw-r--r--src/UpdateArchive.cxx165
-rw-r--r--src/UpdateArchive.hxx51
-rw-r--r--src/UpdateContainer.cxx119
-rw-r--r--src/UpdateContainer.hxx36
-rw-r--r--src/UpdateDatabase.cxx104
-rw-r--r--src/UpdateDatabase.hxx50
-rw-r--r--src/UpdateGlue.cxx186
-rw-r--r--src/UpdateGlue.hxx40
-rw-r--r--src/UpdateIO.cxx113
-rw-r--r--src/UpdateIO.hxx50
-rw-r--r--src/UpdateInternal.hxx28
-rw-r--r--src/UpdateQueue.cxx66
-rw-r--r--src/UpdateQueue.hxx31
-rw-r--r--src/UpdateRemove.cxx96
-rw-r--r--src/UpdateRemove.hxx38
-rw-r--r--src/UpdateSong.cxx112
-rw-r--r--src/UpdateSong.hxx34
-rw-r--r--src/UpdateWalk.cxx488
-rw-r--r--src/UpdateWalk.hxx37
-rw-r--r--src/Volume.cxx138
-rw-r--r--src/Volume.hxx47
-rw-r--r--src/Win32Main.cxx171
-rw-r--r--src/ZeroconfAvahi.cxx268
-rw-r--r--src/ZeroconfAvahi.hxx31
-rw-r--r--src/ZeroconfBonjour.cxx104
-rw-r--r--src/ZeroconfBonjour.hxx31
-rw-r--r--src/ZeroconfGlue.cxx79
-rw-r--r--src/ZeroconfGlue.hxx47
-rw-r--r--src/ZeroconfInternal.hxx26
-rw-r--r--src/ape.c115
-rw-r--r--src/ape.h42
-rw-r--r--src/archive/Bzip2ArchivePlugin.cxx298
-rw-r--r--src/archive/Bzip2ArchivePlugin.hxx25
-rw-r--r--src/archive/Iso9660ArchivePlugin.cxx266
-rw-r--r--src/archive/Iso9660ArchivePlugin.hxx25
-rw-r--r--src/archive/ZzipArchivePlugin.cxx230
-rw-r--r--src/archive/ZzipArchivePlugin.hxx25
-rw-r--r--src/archive/bz2_archive_plugin.c314
-rw-r--r--src/archive/bz2_archive_plugin.h25
-rw-r--r--src/archive/iso9660_archive_plugin.c290
-rw-r--r--src/archive/iso9660_archive_plugin.h25
-rw-r--r--src/archive/zzip_archive_plugin.c246
-rw-r--r--src/archive/zzip_archive_plugin.h25
-rw-r--r--src/archive_api.c111
-rw-r--r--src/archive_api.h38
-rw-r--r--src/archive_internal.h34
-rw-r--r--src/archive_list.c90
-rw-r--r--src/archive_list.h47
-rw-r--r--src/archive_plugin.c94
-rw-r--r--src/archive_plugin.h109
-rw-r--r--src/audio_check.c74
-rw-r--r--src/audio_check.h55
-rw-r--r--src/audio_config.c59
-rw-r--r--src/audio_config.h33
-rw-r--r--src/audio_format.c87
-rw-r--r--src/audio_format.h307
-rw-r--r--src/audio_parser.c222
-rw-r--r--src/audio_parser.h49
-rw-r--r--src/buffer.c137
-rw-r--r--src/buffer.h67
-rw-r--r--src/chunk.c102
-rw-r--r--src/chunk.h151
-rw-r--r--src/client.c41
-rw-r--r--src/client.h79
-rw-r--r--src/client_event.c108
-rw-r--r--src/client_expire.c90
-rw-r--r--src/client_file.c70
-rw-r--r--src/client_file.h42
-rw-r--r--src/client_global.c73
-rw-r--r--src/client_idle.c96
-rw-r--r--src/client_idle.h45
-rw-r--r--src/client_internal.h175
-rw-r--r--src/client_list.c69
-rw-r--r--src/client_message.c96
-rw-r--r--src/client_message.h72
-rw-r--r--src/client_new.c165
-rw-r--r--src/client_process.c146
-rw-r--r--src/client_read.c113
-rw-r--r--src/client_subscribe.c123
-rw-r--r--src/client_subscribe.h59
-rw-r--r--src/client_write.c284
-rw-r--r--src/clock.h14
-rw-r--r--src/cmdline.c253
-rw-r--r--src/cmdline.h38
-rw-r--r--src/command.c2302
-rw-r--r--src/command.h49
-rw-r--r--src/conf.c666
-rw-r--r--src/conf.h202
-rw-r--r--src/crossfade.c136
-rw-r--r--src/crossfade.h51
-rw-r--r--src/cue/CueParser.cxx323
-rw-r--r--src/cue/CueParser.hxx131
-rw-r--r--src/cue/cue_parser.c401
-rw-r--r--src/cue/cue_parser.h58
-rw-r--r--src/database.c170
-rw-r--r--src/database.h101
-rw-r--r--src/db/ProxyDatabasePlugin.cxx476
-rw-r--r--src/db/ProxyDatabasePlugin.hxx27
-rw-r--r--src/db/SimpleDatabasePlugin.cxx349
-rw-r--r--src/db/SimpleDatabasePlugin.hxx102
-rw-r--r--src/db/simple_db_plugin.c357
-rw-r--r--src/db/simple_db_plugin.h42
-rw-r--r--src/dbUtils.c209
-rw-r--r--src/dbUtils.h56
-rw-r--r--src/db_internal.h35
-rw-r--r--src/db_lock.c33
-rw-r--r--src/db_lock.h84
-rw-r--r--src/db_plugin.h156
-rw-r--r--src/db_print.c393
-rw-r--r--src/db_print.h71
-rw-r--r--src/db_save.c179
-rw-r--r--src/db_save.h35
-rw-r--r--src/db_selection.h56
-rw-r--r--src/db_visitor.h54
-rw-r--r--src/decoder/AdPlugDecoderPlugin.cxx144
-rw-r--r--src/decoder/AdPlugDecoderPlugin.h25
-rw-r--r--src/decoder/AudiofileDecoderPlugin.cxx264
-rw-r--r--src/decoder/AudiofileDecoderPlugin.hxx25
-rw-r--r--src/decoder/DsdLib.cxx169
-rw-r--r--src/decoder/DsdLib.hxx51
-rw-r--r--src/decoder/DsdiffDecoderPlugin.cxx530
-rw-r--r--src/decoder/DsdiffDecoderPlugin.hxx25
-rw-r--r--src/decoder/DsfDecoderPlugin.cxx360
-rw-r--r--src/decoder/DsfDecoderPlugin.hxx25
-rw-r--r--src/decoder/FaadDecoderPlugin.cxx503
-rw-r--r--src/decoder/FaadDecoderPlugin.hxx25
-rw-r--r--src/decoder/FfmpegDecoderPlugin.cxx687
-rw-r--r--src/decoder/FfmpegDecoderPlugin.hxx25
-rw-r--r--src/decoder/FfmpegMetaData.cxx79
-rw-r--r--src/decoder/FfmpegMetaData.hxx40
-rw-r--r--src/decoder/FlacCommon.cxx199
-rw-r--r--src/decoder/FlacCommon.hxx97
-rw-r--r--src/decoder/FlacDecoderPlugin.cxx381
-rw-r--r--src/decoder/FlacDecoderPlugin.h26
-rw-r--r--src/decoder/FlacIOHandle.cxx114
-rw-r--r--src/decoder/FlacIOHandle.hxx45
-rw-r--r--src/decoder/FlacInput.cxx149
-rw-r--r--src/decoder/FlacInput.hxx72
-rw-r--r--src/decoder/FlacMetadata.cxx249
-rw-r--r--src/decoder/FlacMetadata.hxx140
-rw-r--r--src/decoder/FlacPcm.cxx110
-rw-r--r--src/decoder/FlacPcm.hxx33
-rw-r--r--src/decoder/FluidsynthDecoderPlugin.cxx224
-rw-r--r--src/decoder/FluidsynthDecoderPlugin.hxx25
-rw-r--r--src/decoder/GmeDecoderPlugin.cxx289
-rw-r--r--src/decoder/GmeDecoderPlugin.hxx25
-rw-r--r--src/decoder/MadDecoderPlugin.cxx1181
-rw-r--r--src/decoder/MadDecoderPlugin.hxx25
-rw-r--r--src/decoder/MikmodDecoderPlugin.cxx242
-rw-r--r--src/decoder/MikmodDecoderPlugin.hxx25
-rw-r--r--src/decoder/ModplugDecoderPlugin.cxx200
-rw-r--r--src/decoder/ModplugDecoderPlugin.hxx25
-rw-r--r--src/decoder/MpcdecDecoderPlugin.cxx277
-rw-r--r--src/decoder/MpcdecDecoderPlugin.hxx25
-rw-r--r--src/decoder/Mpg123DecoderPlugin.cxx250
-rw-r--r--src/decoder/Mpg123DecoderPlugin.hxx25
-rw-r--r--src/decoder/OggCodec.cxx50
-rw-r--r--src/decoder/OggCodec.hxx39
-rw-r--r--src/decoder/OggFind.cxx37
-rw-r--r--src/decoder/OggFind.hxx38
-rw-r--r--src/decoder/OggSyncState.hxx78
-rw-r--r--src/decoder/OggUtil.cxx118
-rw-r--r--src/decoder/OggUtil.hxx87
-rw-r--r--src/decoder/OpusDecoderPlugin.cxx400
-rw-r--r--src/decoder/OpusDecoderPlugin.h25
-rw-r--r--src/decoder/OpusHead.cxx44
-rw-r--r--src/decoder/OpusHead.hxx30
-rw-r--r--src/decoder/OpusReader.hxx100
-rw-r--r--src/decoder/OpusTags.cxx77
-rw-r--r--src/decoder/OpusTags.hxx31
-rw-r--r--src/decoder/PcmDecoderPlugin.cxx119
-rw-r--r--src/decoder/PcmDecoderPlugin.hxx33
-rw-r--r--src/decoder/SndfileDecoderPlugin.cxx261
-rw-r--r--src/decoder/SndfileDecoderPlugin.hxx25
-rw-r--r--src/decoder/VorbisComments.cxx150
-rw-r--r--src/decoder/VorbisComments.hxx39
-rw-r--r--src/decoder/VorbisDecoderPlugin.cxx357
-rw-r--r--src/decoder/VorbisDecoderPlugin.h25
-rw-r--r--src/decoder/WavpackDecoderPlugin.cxx599
-rw-r--r--src/decoder/WavpackDecoderPlugin.hxx25
-rw-r--r--src/decoder/WildmidiDecoderPlugin.cxx155
-rw-r--r--src/decoder/WildmidiDecoderPlugin.hxx25
-rw-r--r--src/decoder/XiphTags.cxx28
-rw-r--r--src/decoder/XiphTags.hxx28
-rw-r--r--src/decoder/_flac_common.c228
-rw-r--r--src/decoder/_flac_common.h105
-rw-r--r--src/decoder/_ogg_common.c46
-rw-r--r--src/decoder/_ogg_common.h33
-rw-r--r--src/decoder/audiofile_decoder_plugin.c258
-rw-r--r--src/decoder/dsdiff_decoder_plugin.c397
-rw-r--r--src/decoder/dsdiff_decoder_plugin.h25
-rw-r--r--src/decoder/dsdlib.c112
-rw-r--r--src/decoder/dsdlib.h42
-rw-r--r--src/decoder/dsf_decoder_plugin.c338
-rw-r--r--src/decoder/dsf_decoder_plugin.h25
-rw-r--r--src/decoder/faad_decoder_plugin.c515
-rw-r--r--src/decoder/ffmpeg_decoder_plugin.c814
-rw-r--r--src/decoder/ffmpeg_metadata.c85
-rw-r--r--src/decoder/ffmpeg_metadata.h41
-rw-r--r--src/decoder/flac_compat.h114
-rw-r--r--src/decoder/flac_decoder_plugin.c486
-rw-r--r--src/decoder/flac_metadata.c323
-rw-r--r--src/decoder/flac_metadata.h64
-rw-r--r--src/decoder/flac_pcm.c110
-rw-r--r--src/decoder/flac_pcm.h33
-rw-r--r--src/decoder/fluidsynth_decoder_plugin.c219
-rw-r--r--src/decoder/gme_decoder_plugin.c257
-rw-r--r--src/decoder/mad_decoder_plugin.c1203
-rw-r--r--src/decoder/mikmod_decoder_plugin.c239
-rw-r--r--src/decoder/modplug_decoder_plugin.c194
-rw-r--r--src/decoder/mp4ff_decoder_plugin.c448
-rw-r--r--src/decoder/mpcdec_decoder_plugin.c347
-rw-r--r--src/decoder/mpg123_decoder_plugin.c245
-rw-r--r--src/decoder/pcm_decoder_plugin.c105
-rw-r--r--src/decoder/pcm_decoder_plugin.h33
-rw-r--r--src/decoder/sidplay_decoder_plugin.cxx34
-rw-r--r--src/decoder/sndfile_decoder_plugin.c255
-rw-r--r--src/decoder/vorbis_comments.c156
-rw-r--r--src/decoder/vorbis_comments.h40
-rw-r--r--src/decoder/vorbis_decoder_plugin.c314
-rw-r--r--src/decoder/wavpack_decoder_plugin.c596
-rw-r--r--src/decoder/wildmidi_decoder_plugin.c150
-rw-r--r--src/decoder_api.c567
-rw-r--r--src/decoder_api.h173
-rw-r--r--src/decoder_buffer.c167
-rw-r--r--src/decoder_buffer.h106
-rw-r--r--src/decoder_command.h30
-rw-r--r--src/decoder_control.c190
-rw-r--r--src/decoder_control.h277
-rw-r--r--src/decoder_internal.c96
-rw-r--r--src/decoder_internal.h100
-rw-r--r--src/decoder_list.c235
-rw-r--r--src/decoder_list.h65
-rw-r--r--src/decoder_plugin.c47
-rw-r--r--src/decoder_plugin.h207
-rw-r--r--src/decoder_print.c53
-rw-r--r--src/decoder_print.h28
-rw-r--r--src/decoder_thread.c510
-rw-r--r--src/decoder_thread.h28
-rw-r--r--src/despotify_utils.c121
-rw-r--r--src/despotify_utils.h67
-rw-r--r--src/directory.c314
-rw-r--r--src/directory.h262
-rw-r--r--src/directory_save.c185
-rw-r--r--src/directory_save.h37
-rw-r--r--src/dsd2pcm/dsd2pcm.hpp41
-rw-r--r--src/dsd2pcm/noiseshape.hpp46
-rw-r--r--src/dummy.cxx30
-rw-r--r--src/encoder/FlacEncoderPlugin.cxx346
-rw-r--r--src/encoder/FlacEncoderPlugin.hxx25
-rw-r--r--src/encoder/LameEncoderPlugin.cxx300
-rw-r--r--src/encoder/LameEncoderPlugin.hxx25
-rw-r--r--src/encoder/NullEncoderPlugin.cxx119
-rw-r--r--src/encoder/NullEncoderPlugin.hxx25
-rw-r--r--src/encoder/OggStream.hxx128
-rw-r--r--src/encoder/OpusEncoderPlugin.cxx425
-rw-r--r--src/encoder/OpusEncoderPlugin.hxx25
-rw-r--r--src/encoder/TwolameEncoderPlugin.cxx308
-rw-r--r--src/encoder/TwolameEncoderPlugin.hxx25
-rw-r--r--src/encoder/VorbisEncoderPlugin.cxx373
-rw-r--r--src/encoder/VorbisEncoderPlugin.hxx25
-rw-r--r--src/encoder/WaveEncoderPlugin.cxx276
-rw-r--r--src/encoder/WaveEncoderPlugin.hxx25
-rw-r--r--src/encoder/flac_encoder.c363
-rw-r--r--src/encoder/lame_encoder.c300
-rw-r--r--src/encoder/null_encoder.c120
-rw-r--r--src/encoder/twolame_encoder.c308
-rw-r--r--src/encoder/vorbis_encoder.c407
-rw-r--r--src/encoder/wave_encoder.c278
-rw-r--r--src/encoder_api.h33
-rw-r--r--src/encoder_list.c61
-rw-r--r--src/encoder_list.h43
-rw-r--r--src/encoder_plugin.h336
-rw-r--r--src/event/BufferedSocket.cxx150
-rw-r--r--src/event/BufferedSocket.hxx104
-rw-r--r--src/event/FullyBufferedSocket.cxx132
-rw-r--r--src/event/FullyBufferedSocket.hxx63
-rw-r--r--src/event/Loop.hxx88
-rw-r--r--src/event/MultiSocketMonitor.cxx107
-rw-r--r--src/event/MultiSocketMonitor.hxx125
-rw-r--r--src/event/ServerSocket.cxx438
-rw-r--r--src/event/ServerSocket.hxx121
-rw-r--r--src/event/SocketMonitor.cxx167
-rw-r--r--src/event/SocketMonitor.hxx148
-rw-r--r--src/event/TimeoutMonitor.cxx61
-rw-r--r--src/event/TimeoutMonitor.hxx57
-rw-r--r--src/event/WakeFD.cxx225
-rw-r--r--src/event/WakeFD.hxx80
-rw-r--r--src/event_pipe.c164
-rw-r--r--src/event_pipe.h71
-rw-r--r--src/exclude.c95
-rw-r--r--src/exclude.h51
-rw-r--r--src/fd_util.c14
-rw-r--r--src/fd_util.h19
-rw-r--r--src/fifo_buffer.c210
-rw-r--r--src/fifo_buffer.h153
-rw-r--r--src/filter/AutoConvertFilterPlugin.cxx126
-rw-r--r--src/filter/AutoConvertFilterPlugin.hxx34
-rw-r--r--src/filter/ChainFilterPlugin.cxx185
-rw-r--r--src/filter/ChainFilterPlugin.hxx48
-rw-r--r--src/filter/ConvertFilterPlugin.cxx119
-rw-r--r--src/filter/ConvertFilterPlugin.hxx35
-rw-r--r--src/filter/NormalizeFilterPlugin.cxx83
-rw-r--r--src/filter/NullFilterPlugin.cxx61
-rw-r--r--src/filter/ReplayGainFilterPlugin.cxx238
-rw-r--r--src/filter/ReplayGainFilterPlugin.hxx52
-rw-r--r--src/filter/RouteFilterPlugin.cxx322
-rw-r--r--src/filter/VolumeFilterPlugin.cxx147
-rw-r--r--src/filter/VolumeFilterPlugin.hxx31
-rw-r--r--src/filter/autoconvert_filter_plugin.c169
-rw-r--r--src/filter/autoconvert_filter_plugin.h34
-rw-r--r--src/filter/chain_filter_plugin.c213
-rw-r--r--src/filter/chain_filter_plugin.h48
-rw-r--r--src/filter/convert_filter_plugin.c146
-rw-r--r--src/filter/convert_filter_plugin.h36
-rw-r--r--src/filter/normalize_filter_plugin.c112
-rw-r--r--src/filter/null_filter_plugin.c93
-rw-r--r--src/filter/replay_gain_filter_plugin.c245
-rw-r--r--src/filter/replay_gain_filter_plugin.h50
-rw-r--r--src/filter/route_filter_plugin.c348
-rw-r--r--src/filter/volume_filter_plugin.c158
-rw-r--r--src/filter/volume_filter_plugin.h31
-rw-r--r--src/filter_config.c119
-rw-r--r--src/filter_config.h47
-rw-r--r--src/filter_internal.h38
-rw-r--r--src/filter_plugin.c116
-rw-r--r--src/filter_plugin.h150
-rw-r--r--src/filter_registry.c44
-rw-r--r--src/filter_registry.h40
-rw-r--r--src/fs/DirectoryReader.hxx87
-rw-r--r--src/fs/FileSystem.cxx43
-rw-r--r--src/fs/FileSystem.hxx163
-rw-r--r--src/fs/Path.cxx150
-rw-r--r--src/fs/Path.hxx267
-rw-r--r--src/gcc.h47
-rw-r--r--src/gerror.h25
-rw-r--r--src/glib_compat.h11
-rw-r--r--src/glib_socket.h40
-rw-r--r--src/icy_metadata.c182
-rw-r--r--src/icy_metadata.h99
-rw-r--r--src/icy_server.c149
-rw-r--r--src/icy_server.h36
-rw-r--r--src/idle.c94
-rw-r--r--src/idle.h95
-rw-r--r--src/inotify_queue.c135
-rw-r--r--src/inotify_queue.h32
-rw-r--r--src/inotify_source.c167
-rw-r--r--src/inotify_source.h61
-rw-r--r--src/inotify_update.c374
-rw-r--r--src/inotify_update.h47
-rw-r--r--src/input/ArchiveInputPlugin.cxx93
-rw-r--r--src/input/ArchiveInputPlugin.hxx25
-rw-r--r--src/input/CdioParanoiaInputPlugin.cxx379
-rw-r--r--src/input/CdioParanoiaInputPlugin.hxx28
-rw-r--r--src/input/CurlInputPlugin.cxx1184
-rw-r--r--src/input/CurlInputPlugin.hxx27
-rw-r--r--src/input/DespotifyInputPlugin.cxx242
-rw-r--r--src/input/DespotifyInputPlugin.hxx25
-rw-r--r--src/input/FfmpegInputPlugin.cxx184
-rw-r--r--src/input/FfmpegInputPlugin.hxx28
-rw-r--r--src/input/FileInputPlugin.cxx164
-rw-r--r--src/input/FileInputPlugin.hxx25
-rw-r--r--src/input/MmsInputPlugin.cxx147
-rw-r--r--src/input/MmsInputPlugin.hxx (renamed from src/input/mms_input_plugin.h)0
-rw-r--r--src/input/RewindInputPlugin.cxx257
-rw-r--r--src/input/RewindInputPlugin.hxx37
-rw-r--r--src/input/archive_input_plugin.c81
-rw-r--r--src/input/archive_input_plugin.h25
-rw-r--r--src/input/cdio_paranoia_input_plugin.c371
-rw-r--r--src/input/cdio_paranoia_input_plugin.h28
-rw-r--r--src/input/curl_input_plugin.c1301
-rw-r--r--src/input/curl_input_plugin.h27
-rw-r--r--src/input/despotify_input_plugin.c230
-rw-r--r--src/input/despotify_input_plugin.h25
-rw-r--r--src/input/ffmpeg_input_plugin.c204
-rw-r--r--src/input/ffmpeg_input_plugin.h28
-rw-r--r--src/input/file_input_plugin.c160
-rw-r--r--src/input/file_input_plugin.h25
-rw-r--r--src/input/mms_input_plugin.c140
-rw-r--r--src/input/rewind_input_plugin.c256
-rw-r--r--src/input/rewind_input_plugin.h37
-rw-r--r--src/input/soup_input_plugin.c473
-rw-r--r--src/input/soup_input_plugin.h25
-rw-r--r--src/input_init.c104
-rw-r--r--src/input_init.h42
-rw-r--r--src/input_internal.c73
-rw-r--r--src/input_internal.h43
-rw-r--r--src/input_plugin.h89
-rw-r--r--src/input_registry.c80
-rw-r--r--src/input_registry.h45
-rw-r--r--src/input_stream.c243
-rw-r--r--src/input_stream.h131
-rw-r--r--src/io_error.h51
-rw-r--r--src/io_thread.c199
-rw-r--r--src/io_thread.h80
-rw-r--r--src/listen.c153
-rw-r--r--src/listen.h34
-rw-r--r--src/locate.c239
-rw-r--r--src/locate.h92
-rw-r--r--src/log.c341
-rw-r--r--src/log.h54
-rw-r--r--src/ls.c98
-rw-r--r--src/ls.cxx100
-rw-r--r--src/ls.h46
-rw-r--r--src/ls.hxx45
-rw-r--r--src/main.c541
-rw-r--r--src/main.h74
-rw-r--r--src/main_win32.c154
-rw-r--r--src/mapper.c275
-rw-r--r--src/mapper.h144
-rw-r--r--src/mixer/AlsaMixerPlugin.cxx370
-rw-r--r--src/mixer/OssMixerPlugin.cxx256
-rw-r--r--src/mixer/PulseMixerPlugin.cxx235
-rw-r--r--src/mixer/PulseMixerPlugin.hxx47
-rw-r--r--src/mixer/RoarMixerPlugin.cxx74
-rw-r--r--src/mixer/SoftwareMixerPlugin.cxx113
-rw-r--r--src/mixer/SoftwareMixerPlugin.hxx33
-rw-r--r--src/mixer/WinmmMixerPlugin.cxx121
-rw-r--r--src/mixer/alsa_mixer_plugin.c431
-rw-r--r--src/mixer/oss_mixer_plugin.c216
-rw-r--r--src/mixer/pulse_mixer_plugin.c236
-rw-r--r--src/mixer/pulse_mixer_plugin.h39
-rw-r--r--src/mixer/roar_mixer_plugin.c104
-rw-r--r--src/mixer/software_mixer_plugin.c109
-rw-r--r--src/mixer/software_mixer_plugin.h33
-rw-r--r--src/mixer/winmm_mixer_plugin.c114
-rw-r--r--src/mixer_all.c181
-rw-r--r--src/mixer_all.h62
-rw-r--r--src/mixer_api.c33
-rw-r--r--src/mixer_api.h52
-rw-r--r--src/mixer_control.c185
-rw-r--r--src/mixer_control.h63
-rw-r--r--src/mixer_list.h35
-rw-r--r--src/mixer_plugin.h99
-rw-r--r--src/mixer_type.c39
-rw-r--r--src/mixer_type.h47
-rw-r--r--src/mpd_error.h1
-rw-r--r--src/notify.c58
-rw-r--r--src/notify.cxx45
-rw-r--r--src/notify.h53
-rw-r--r--src/notify.hxx53
-rw-r--r--src/output/AlsaOutputPlugin.cxx851
-rw-r--r--src/output/AlsaOutputPlugin.hxx25
-rw-r--r--src/output/AoOutputPlugin.cxx291
-rw-r--r--src/output/AoOutputPlugin.hxx25
-rw-r--r--src/output/FifoOutputPlugin.cxx326
-rw-r--r--src/output/FifoOutputPlugin.hxx25
-rw-r--r--src/output/HttpdClient.cxx444
-rw-r--r--src/output/HttpdClient.hxx186
-rw-r--r--src/output/HttpdInternal.hxx206
-rw-r--r--src/output/HttpdOutputPlugin.cxx568
-rw-r--r--src/output/HttpdOutputPlugin.hxx25
-rw-r--r--src/output/JackOutputPlugin.cxx776
-rw-r--r--src/output/JackOutputPlugin.hxx25
-rw-r--r--src/output/NullOutputPlugin.cxx143
-rw-r--r--src/output/NullOutputPlugin.hxx25
-rw-r--r--src/output/OSXOutputPlugin.cxx436
-rw-r--r--src/output/OSXOutputPlugin.hxx25
-rw-r--r--src/output/OpenALOutputPlugin.cxx293
-rw-r--r--src/output/OpenALOutputPlugin.hxx25
-rw-r--r--src/output/OssOutputPlugin.cxx787
-rw-r--r--src/output/OssOutputPlugin.hxx25
-rw-r--r--src/output/PipeOutputPlugin.cxx152
-rw-r--r--src/output/PipeOutputPlugin.hxx25
-rw-r--r--src/output/PulseOutputPlugin.cxx956
-rw-r--r--src/output/PulseOutputPlugin.hxx55
-rw-r--r--src/output/RecorderOutputPlugin.cxx274
-rw-r--r--src/output/RecorderOutputPlugin.hxx25
-rw-r--r--src/output/RoarOutputPlugin.cxx399
-rw-r--r--src/output/RoarOutputPlugin.hxx33
-rw-r--r--src/output/ShoutOutputPlugin.cxx560
-rw-r--r--src/output/ShoutOutputPlugin.hxx25
-rw-r--r--src/output/SolarisOutputPlugin.cxx218
-rw-r--r--src/output/SolarisOutputPlugin.hxx25
-rw-r--r--src/output/WinmmOutputPlugin.cxx360
-rw-r--r--src/output/WinmmOutputPlugin.hxx42
-rw-r--r--src/output/alsa_output_plugin.c819
-rw-r--r--src/output/alsa_output_plugin.h25
-rw-r--r--src/output/ao_output_plugin.c264
-rw-r--r--src/output/ao_output_plugin.h25
-rw-r--r--src/output/ffado_output_plugin.c359
-rw-r--r--src/output/ffado_output_plugin.h25
-rw-r--r--src/output/fifo_output_plugin.c315
-rw-r--r--src/output/fifo_output_plugin.h25
-rw-r--r--src/output/httpd_client.c764
-rw-r--r--src/output/httpd_client.h71
-rw-r--r--src/output/httpd_internal.h138
-rw-r--r--src/output/httpd_output_plugin.c623
-rw-r--r--src/output/httpd_output_plugin.h25
-rw-r--r--src/output/jack_output_plugin.c755
-rw-r--r--src/output/jack_output_plugin.h25
-rw-r--r--src/output/mvp_output_plugin.c344
-rw-r--r--src/output/mvp_output_plugin.h25
-rw-r--r--src/output/null_output_plugin.c129
-rw-r--r--src/output/null_output_plugin.h25
-rw-r--r--src/output/openal_output_plugin.c279
-rw-r--r--src/output/openal_output_plugin.h25
-rw-r--r--src/output/oss_output_plugin.c788
-rw-r--r--src/output/oss_output_plugin.h25
-rw-r--r--src/output/osx_output_plugin.c438
-rw-r--r--src/output/osx_output_plugin.h25
-rw-r--r--src/output/pipe_output_plugin.c121
-rw-r--r--src/output/pipe_output_plugin.h25
-rw-r--r--src/output/pulse_output_plugin.c955
-rw-r--r--src/output/pulse_output_plugin.h49
-rw-r--r--src/output/recorder_output_plugin.c251
-rw-r--r--src/output/recorder_output_plugin.h25
-rw-r--r--src/output/roar_output_plugin.c401
-rw-r--r--src/output/roar_output_plugin.h35
-rw-r--r--src/output/shout_output_plugin.c555
-rw-r--r--src/output/shout_output_plugin.h25
-rw-r--r--src/output/solaris_output_plugin.c203
-rw-r--r--src/output/solaris_output_plugin.h25
-rw-r--r--src/output/winmm_output_plugin.c356
-rw-r--r--src/output/winmm_output_plugin.h37
-rw-r--r--src/output_all.c590
-rw-r--r--src/output_all.h166
-rw-r--r--src/output_api.h29
-rw-r--r--src/output_command.c87
-rw-r--r--src/output_command.h46
-rw-r--r--src/output_control.c336
-rw-r--r--src/output_control.h94
-rw-r--r--src/output_finish.c60
-rw-r--r--src/output_init.c332
-rw-r--r--src/output_internal.h269
-rw-r--r--src/output_list.c106
-rw-r--r--src/output_list.h33
-rw-r--r--src/output_plugin.c109
-rw-r--r--src/output_plugin.h210
-rw-r--r--src/output_print.c45
-rw-r--r--src/output_print.h33
-rw-r--r--src/output_state.c91
-rw-r--r--src/output_state.h45
-rw-r--r--src/output_thread.c685
-rw-r--r--src/output_thread.h27
-rw-r--r--src/page.c89
-rw-r--r--src/page.h94
-rw-r--r--src/path.c122
-rw-r--r--src/path.h55
-rw-r--r--src/pcm/PcmBuffer.cxx56
-rw-r--r--src/pcm/PcmBuffer.hxx64
-rw-r--r--src/pcm/PcmChannels.cxx290
-rw-r--r--src/pcm/PcmChannels.hxx97
-rw-r--r--src/pcm/PcmConvert.cxx309
-rw-r--r--src/pcm/PcmConvert.hxx112
-rw-r--r--src/pcm/PcmDither.cxx89
-rw-r--r--src/pcm/PcmDither.hxx44
-rw-r--r--src/pcm/PcmDsd.cxx81
-rw-r--r--src/pcm/PcmDsd.hxx46
-rw-r--r--src/pcm/PcmDsdUsb.cxx96
-rw-r--r--src/pcm/PcmDsdUsb.hxx41
-rw-r--r--src/pcm/PcmExport.cxx145
-rw-r--r--src/pcm/PcmExport.hxx127
-rw-r--r--src/pcm/PcmFormat.cxx500
-rw-r--r--src/pcm/PcmFormat.hxx93
-rw-r--r--src/pcm/PcmMix.cxx209
-rw-r--r--src/pcm/PcmMix.hxx49
-rw-r--r--src/pcm/PcmPrng.hxx33
-rw-r--r--src/pcm/PcmResample.cxx149
-rw-r--r--src/pcm/PcmResample.hxx137
-rw-r--r--src/pcm/PcmResampleFallback.cxx106
-rw-r--r--src/pcm/PcmResampleInternal.hxx91
-rw-r--r--src/pcm/PcmResampleLibsamplerate.cxx290
-rw-r--r--src/pcm/PcmUtils.hxx66
-rw-r--r--src/pcm/PcmVolume.cxx193
-rw-r--r--src/pcm/PcmVolume.hxx81
-rw-r--r--src/pcm/dsd2pcm/dsd2pcm.c (renamed from src/dsd2pcm/dsd2pcm.c)0
-rw-r--r--src/pcm/dsd2pcm/dsd2pcm.h (renamed from src/dsd2pcm/dsd2pcm.h)0
-rw-r--r--src/pcm/dsd2pcm/dsd2pcm.hpp39
-rw-r--r--src/pcm/dsd2pcm/info.txt (renamed from src/dsd2pcm/info.txt)0
-rw-r--r--src/pcm/dsd2pcm/main.cpp (renamed from src/dsd2pcm/main.cpp)0
-rw-r--r--src/pcm/dsd2pcm/noiseshape.c (renamed from src/dsd2pcm/noiseshape.c)0
-rw-r--r--src/pcm/dsd2pcm/noiseshape.h (renamed from src/dsd2pcm/noiseshape.h)0
-rw-r--r--src/pcm/dsd2pcm/noiseshape.hpp43
-rw-r--r--src/pcm/pcm_pack.c (renamed from src/pcm_pack.c)0
-rw-r--r--src/pcm/pcm_pack.h (renamed from src/pcm_pack.h)0
-rw-r--r--src/pcm_buffer.c58
-rw-r--r--src/pcm_buffer.h77
-rw-r--r--src/pcm_channels.c246
-rw-r--r--src/pcm_channels.h80
-rw-r--r--src/pcm_convert.c379
-rw-r--r--src/pcm_convert.h94
-rw-r--r--src/pcm_dither.c93
-rw-r--r--src/pcm_dither.h45
-rw-r--r--src/pcm_dsd.c85
-rw-r--r--src/pcm_dsd.h52
-rw-r--r--src/pcm_dsd_usb.c97
-rw-r--r--src/pcm_dsd_usb.h42
-rw-r--r--src/pcm_export.c160
-rw-r--r--src/pcm_export.h147
-rw-r--r--src/pcm_format.c487
-rw-r--r--src/pcm_format.h93
-rw-r--r--src/pcm_mix.c280
-rw-r--r--src/pcm_mix.h49
-rw-r--r--src/pcm_prng.h33
-rw-r--r--src/pcm_resample.c159
-rw-r--r--src/pcm_resample.h164
-rw-r--r--src/pcm_resample_fallback.c118
-rw-r--r--src/pcm_resample_internal.h97
-rw-r--r--src/pcm_resample_libsamplerate.c312
-rw-r--r--src/pcm_utils.h94
-rw-r--r--src/pcm_volume.c190
-rw-r--r--src/pcm_volume.h81
-rw-r--r--src/permission.c138
-rw-r--r--src/permission.h38
-rw-r--r--src/pipe.c194
-rw-r--r--src/pipe.h113
-rw-r--r--src/player_control.c389
-rw-r--r--src/player_control.h287
-rw-r--r--src/player_thread.c1175
-rw-r--r--src/player_thread.h45
-rw-r--r--src/playlist.c452
-rw-r--r--src/playlist.h256
-rw-r--r--src/playlist/AsxPlaylistPlugin.cxx283
-rw-r--r--src/playlist/AsxPlaylistPlugin.hxx25
-rw-r--r--src/playlist/CuePlaylistPlugin.cxx110
-rw-r--r--src/playlist/CuePlaylistPlugin.hxx25
-rw-r--r--src/playlist/DespotifyPlaylistPlugin.cxx146
-rw-r--r--src/playlist/DespotifyPlaylistPlugin.hxx25
-rw-r--r--src/playlist/EmbeddedCuePlaylistPlugin.cxx187
-rw-r--r--src/playlist/EmbeddedCuePlaylistPlugin.hxx25
-rw-r--r--src/playlist/ExtM3uPlaylistPlugin.cxx163
-rw-r--r--src/playlist/ExtM3uPlaylistPlugin.hxx25
-rw-r--r--src/playlist/LastFMPlaylistPlugin.cxx297
-rw-r--r--src/playlist/LastFMPlaylistPlugin.hxx25
-rw-r--r--src/playlist/M3uPlaylistPlugin.cxx97
-rw-r--r--src/playlist/M3uPlaylistPlugin.hxx25
-rw-r--r--src/playlist/MemoryPlaylistProvider.cxx69
-rw-r--r--src/playlist/MemoryPlaylistProvider.hxx39
-rw-r--r--src/playlist/PlsPlaylistPlugin.cxx180
-rw-r--r--src/playlist/PlsPlaylistPlugin.hxx25
-rw-r--r--src/playlist/RssPlaylistPlugin.cxx281
-rw-r--r--src/playlist/RssPlaylistPlugin.hxx25
-rw-r--r--src/playlist/SoundCloudPlaylistPlugin.cxx417
-rw-r--r--src/playlist/SoundCloudPlaylistPlugin.hxx25
-rw-r--r--src/playlist/XspfPlaylistPlugin.cxx299
-rw-r--r--src/playlist/XspfPlaylistPlugin.hxx25
-rw-r--r--src/playlist/asx_playlist_plugin.c323
-rw-r--r--src/playlist/asx_playlist_plugin.h25
-rw-r--r--src/playlist/cue_playlist_plugin.c108
-rw-r--r--src/playlist/cue_playlist_plugin.h25
-rw-r--r--src/playlist/despotify_playlist_plugin.c217
-rw-r--r--src/playlist/despotify_playlist_plugin.h25
-rw-r--r--src/playlist/embcue_playlist_plugin.c181
-rw-r--r--src/playlist/embcue_playlist_plugin.h25
-rw-r--r--src/playlist/extm3u_playlist_plugin.c162
-rw-r--r--src/playlist/extm3u_playlist_plugin.h25
-rw-r--r--src/playlist/lastfm_playlist_plugin.c296
-rw-r--r--src/playlist/lastfm_playlist_plugin.h25
-rw-r--r--src/playlist/m3u_playlist_plugin.c92
-rw-r--r--src/playlist/m3u_playlist_plugin.h25
-rw-r--r--src/playlist/pls_playlist_plugin.c220
-rw-r--r--src/playlist/pls_playlist_plugin.h25
-rw-r--r--src/playlist/rss_playlist_plugin.c322
-rw-r--r--src/playlist/rss_playlist_plugin.h25
-rw-r--r--src/playlist/soundcloud_playlist_plugin.c448
-rw-r--r--src/playlist/soundcloud_playlist_plugin.h25
-rw-r--r--src/playlist/xspf_playlist_plugin.c343
-rw-r--r--src/playlist/xspf_playlist_plugin.h25
-rw-r--r--src/playlist_any.c71
-rw-r--r--src/playlist_any.h41
-rw-r--r--src/playlist_control.c288
-rw-r--r--src/playlist_database.c79
-rw-r--r--src/playlist_database.h40
-rw-r--r--src/playlist_edit.c492
-rw-r--r--src/playlist_global.c58
-rw-r--r--src/playlist_internal.h56
-rw-r--r--src/playlist_list.c348
-rw-r--r--src/playlist_list.h85
-rw-r--r--src/playlist_mapper.c110
-rw-r--r--src/playlist_mapper.h39
-rw-r--r--src/playlist_plugin.h141
-rw-r--r--src/playlist_print.c199
-rw-r--r--src/playlist_print.h117
-rw-r--r--src/playlist_queue.c97
-rw-r--r--src/playlist_queue.h61
-rw-r--r--src/playlist_save.c150
-rw-r--r--src/playlist_save.h61
-rw-r--r--src/playlist_song.c169
-rw-r--r--src/playlist_song.h37
-rw-r--r--src/playlist_state.c250
-rw-r--r--src/playlist_state.h53
-rw-r--r--src/playlist_vector.c114
-rw-r--r--src/playlist_vector.h80
-rw-r--r--src/protocol/ArgParser.cxx191
-rw-r--r--src/protocol/ArgParser.hxx48
-rw-r--r--src/protocol/Result.cxx57
-rw-r--r--src/protocol/Result.hxx43
-rw-r--r--src/protocol/argparser.c191
-rw-r--r--src/protocol/argparser.h49
-rw-r--r--src/protocol/result.c57
-rw-r--r--src/protocol/result.h44
-rw-r--r--src/queue.c603
-rw-r--r--src/queue.h379
-rw-r--r--src/queue_print.c122
-rw-r--r--src/queue_print.h58
-rw-r--r--src/queue_save.c123
-rw-r--r--src/queue_save.h43
-rw-r--r--src/refcount.h67
-rw-r--r--src/replay_gain_ape.c78
-rw-r--r--src/replay_gain_ape.h32
-rw-r--r--src/replay_gain_config.c150
-rw-r--r--src/replay_gain_config.h10
-rw-r--r--src/replay_gain_info.c48
-rw-r--r--src/replay_gain_info.h16
-rw-r--r--src/resolver.h14
-rw-r--r--src/server_socket.c483
-rw-r--r--src/server_socket.h92
-rw-r--r--src/sig_handlers.c80
-rw-r--r--src/sig_handlers.h25
-rw-r--r--src/socket_util.c100
-rw-r--r--src/socket_util.h56
-rw-r--r--src/song.c107
-rw-r--r--src/song.h120
-rw-r--r--src/song_print.c95
-rw-r--r--src/song_print.h33
-rw-r--r--src/song_save.c133
-rw-r--r--src/song_save.h47
-rw-r--r--src/song_sort.c121
-rw-r--r--src/song_sort.h28
-rw-r--r--src/song_sticker.c167
-rw-r--r--src/song_sticker.h84
-rw-r--r--src/song_update.c203
-rw-r--r--src/state_file.c162
-rw-r--r--src/state_file.h33
-rw-r--r--src/stats.c128
-rw-r--r--src/stats.h5
-rw-r--r--src/sticker.c660
-rw-r--r--src/sticker.h159
-rw-r--r--src/sticker_print.c44
-rw-r--r--src/sticker_print.h39
-rw-r--r--src/stored_playlist.c552
-rw-r--r--src/stored_playlist.h88
-rw-r--r--src/string_util.c47
-rw-r--r--src/string_util.h77
-rw-r--r--src/strset.c148
-rw-r--r--src/strset.h50
-rw-r--r--src/tag.c523
-rw-r--r--src/tag.h239
-rw-r--r--src/tag_ape.c117
-rw-r--r--src/tag_ape.h40
-rw-r--r--src/tag_file.c90
-rw-r--r--src/tag_file.h37
-rw-r--r--src/tag_handler.c60
-rw-r--r--src/tag_handler.h101
-rw-r--r--src/tag_id3.c584
-rw-r--r--src/tag_id3.h63
-rw-r--r--src/tag_internal.h29
-rw-r--r--src/tag_pool.c161
-rw-r--r--src/tag_pool.h42
-rw-r--r--src/tag_print.c48
-rw-r--r--src/tag_print.h30
-rw-r--r--src/tag_rva2.c150
-rw-r--r--src/tag_rva2.h39
-rw-r--r--src/tag_save.c38
-rw-r--r--src/tag_save.h29
-rw-r--r--src/tag_table.h65
-rw-r--r--src/text_file.c68
-rw-r--r--src/text_file.h39
-rw-r--r--src/text_input_stream.c111
-rw-r--r--src/text_input_stream.h52
-rw-r--r--src/thread/Cond.hxx37
-rw-r--r--src/thread/CriticalSection.hxx68
-rw-r--r--src/thread/GLibCond.hxx88
-rw-r--r--src/thread/GLibMutex.hxx90
-rw-r--r--src/thread/Mutex.hxx54
-rw-r--r--src/thread/PosixCond.hxx73
-rw-r--r--src/thread/PosixMutex.hxx62
-rw-r--r--src/thread/WindowsCond.hxx67
-rw-r--r--src/timer.c89
-rw-r--r--src/timer.h51
-rw-r--r--src/tokenizer.c223
-rw-r--r--src/tokenizer.h83
-rw-r--r--src/update.c184
-rw-r--r--src/update.h42
-rw-r--r--src/update_archive.c156
-rw-r--r--src/update_archive.h53
-rw-r--r--src/update_container.c123
-rw-r--r--src/update_container.h37
-rw-r--r--src/update_db.c104
-rw-r--r--src/update_db.h52
-rw-r--r--src/update_internal.h30
-rw-r--r--src/update_io.c117
-rw-r--r--src/update_io.h51
-rw-r--r--src/update_queue.c66
-rw-r--r--src/update_queue.h33
-rw-r--r--src/update_remove.c102
-rw-r--r--src/update_remove.h41
-rw-r--r--src/update_song.c113
-rw-r--r--src/update_song.h35
-rw-r--r--src/update_walk.c508
-rw-r--r--src/update_walk.h39
-rw-r--r--src/uri.c114
-rw-r--r--src/uri.h60
-rw-r--r--src/util/HugeAllocator.cxx87
-rw-r--r--src/util/HugeAllocator.hxx82
-rw-r--r--src/util/LazyRandomEngine.cxx31
-rw-r--r--src/util/LazyRandomEngine.hxx67
-rw-r--r--src/util/Manual.hxx120
-rw-r--r--src/util/PeakBuffer.cxx143
-rw-r--r--src/util/PeakBuffer.hxx66
-rw-r--r--src/util/RefCount.hxx59
-rw-r--r--src/util/SliceBuffer.hxx161
-rw-r--r--src/util/StringUtil.cxx46
-rw-r--r--src/util/StringUtil.hxx80
-rw-r--r--src/util/Tokenizer.cxx201
-rw-r--r--src/util/Tokenizer.hxx99
-rw-r--r--src/util/UriUtil.cxx113
-rw-r--r--src/util/UriUtil.hxx58
-rw-r--r--src/util/bit_reverse.h5
-rw-r--r--src/util/fifo_buffer.c218
-rw-r--r--src/util/fifo_buffer.h164
-rw-r--r--src/util/growing_fifo.c (renamed from src/growing_fifo.c)0
-rw-r--r--src/util/growing_fifo.h (renamed from src/growing_fifo.h)0
-rw-r--r--src/util/list.h53
-rw-r--r--src/utils.c121
-rw-r--r--src/utils.h38
-rw-r--r--src/volume.c145
-rw-r--r--src/volume.h48
-rw-r--r--src/zeroconf-avahi.c267
-rw-r--r--src/zeroconf-bonjour.c97
-rw-r--r--src/zeroconf-internal.h34
-rw-r--r--src/zeroconf.c75
-rw-r--r--src/zeroconf.h37
-rw-r--r--test/DumpDatabase.cxx150
-rw-r--r--test/FakeReplayGainConfig.cxx25
-rw-r--r--test/FakeSong.cxx33
-rw-r--r--test/dump_playlist.c255
-rw-r--r--test/dump_playlist.cxx253
-rw-r--r--test/dump_rva2.c109
-rw-r--r--test/dump_rva2.cxx102
-rw-r--r--test/dump_text_file.c170
-rw-r--r--test/dump_text_file.cxx166
-rw-r--r--test/read_conf.c72
-rw-r--r--test/read_conf.cxx75
-rw-r--r--test/read_mixer.c154
-rw-r--r--test/read_mixer.cxx165
-rw-r--r--test/read_tags.c245
-rw-r--r--test/read_tags.cxx241
-rw-r--r--test/run_convert.c126
-rw-r--r--test/run_convert.cxx126
-rw-r--r--test/run_decoder.c254
-rw-r--r--test/run_decoder.cxx225
-rw-r--r--test/run_encoder.c134
-rw-r--r--test/run_encoder.cxx129
-rw-r--r--test/run_filter.c201
-rw-r--r--test/run_filter.cxx194
-rw-r--r--test/run_inotify.c93
-rw-r--r--test/run_inotify.cxx94
-rw-r--r--test/run_input.c185
-rw-r--r--test/run_input.cxx184
-rw-r--r--test/run_normalize.c73
-rw-r--r--test/run_normalize.cxx72
-rw-r--r--test/run_output.c253
-rw-r--r--test/run_output.cxx253
-rw-r--r--test/run_tcp_connect.c164
-rw-r--r--test/software_volume.c70
-rw-r--r--test/software_volume.cxx70
-rw-r--r--test/test_pcm_all.h56
-rw-r--r--test/test_pcm_all.hxx80
-rw-r--r--test/test_pcm_channels.c101
-rw-r--r--test/test_pcm_channels.cxx90
-rw-r--r--test/test_pcm_dither.c79
-rw-r--r--test/test_pcm_dither.cxx56
-rw-r--r--test/test_pcm_format.cxx116
-rw-r--r--test/test_pcm_main.c42
-rw-r--r--test/test_pcm_main.cxx52
-rw-r--r--test/test_pcm_mix.cxx84
-rw-r--r--test/test_pcm_pack.c89
-rw-r--r--test/test_pcm_pack.cxx75
-rw-r--r--test/test_pcm_util.hxx85
-rw-r--r--test/test_pcm_volume.c190
-rw-r--r--test/test_pcm_volume.cxx171
-rw-r--r--test/test_queue_priority.c175
-rw-r--r--test/test_queue_priority.cxx188
-rw-r--r--test/test_vorbis_encoder.c111
-rw-r--r--test/test_vorbis_encoder.cxx107
-rw-r--r--test/visit_archive.cxx126
-rw-r--r--valgrind.suppressions133
1205 files changed, 87774 insertions, 82919 deletions
diff --git a/.gitignore b/.gitignore
index a876b705e..a20d3c54b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,7 @@
*.la
*.lo
*.o
+*.exe
.deps
.dirstamp
Makefile
@@ -31,7 +32,6 @@ ltmain.sh
missing
mkinstalldirs
mpd
-mpd.exe
mpd.service
stamp-h1
tags
@@ -70,6 +70,7 @@ test/dump_rva2
test/dump_text_file
test/test_byte_reverse
test/test_vorbis_encoder
+test/DumpDatabase
/*.tar.gz
/*.tar.bz2
diff --git a/INSTALL b/INSTALL
index fdebbe71d..185798a76 100644
--- a/INSTALL
+++ b/INSTALL
@@ -37,9 +37,6 @@ Linux. You will need libasound.
FIFO
This is a mostly undocumented, developer plugin to transmit raw data.
-MVP - http://en.wikipedia.org/wiki/Hauppauge_MediaMVP
-A network media player.
-
OSS - http://www.opensound.com
Open Sound System.
@@ -59,9 +56,6 @@ You also need an encoder: either libvorbisenc (ogg), or liblame (mp3).
OpenAL - http://kcat.strangesoft.net/openal.html
Open Audio Library
-libffado - http://www.ffado.org/
-For FireWire audio devices.
-
Optional Input Dependencies
---------------------------
@@ -81,14 +75,17 @@ Alternative for MP3 support.
Ogg Vorbis - http://www.xiph.org/ogg/vorbis/
For Ogg Vorbis support. You will need libogg and libvorbis.
+libopus - http://www.opus-codec.org/
+Opus codec support
+
FLAC - http://flac.sourceforge.net/
-For FLAC support. You will need version 1.1.0 or higher of libflac.
+For FLAC support. You will need version 1.2 or higher of libFLAC.
Audio File - http://www.68k.org/~michael/audiofile/
For WAVE, AIFF, and AU support. You will need libaudiofile.
FAAD2 - http://www.audiocoding.com/
-For MP4/AAC support. You will need libmp4ff.
+For MP4/AAC support.
libmpcdec - http://www.musepack.net/
For Musepack support.
@@ -114,6 +111,9 @@ WAVE, AIFF, and many others.
libwavpack - http://www.wavpack.com/
For WavPack playback.
+libadplug - http://adplug.sourceforge.net/
+For AdLib playback.
+
despotify - https://github.com/SimonKagstrom/despotify
For Spotify playback.
diff --git a/Makefile.am b/Makefile.am
index 90fa3481c..5f0291d7e 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -9,9 +9,13 @@ bin_PROGRAMS = src/mpd
noinst_LIBRARIES = \
libutil.a \
+ libevent.a \
libpcm.a \
+ libconf.a \
libtag.a \
libinput.a \
+ libfs.a \
+ libdb_plugins.a \
libplaylist_plugins.a \
libdecoder_plugins.a \
libfilter_plugins.a \
@@ -19,10 +23,12 @@ noinst_LIBRARIES = \
liboutput_plugins.a
src_mpd_CPPFLAGS = $(AM_CPPFLAGS) \
+ $(LIBMPDCLIENT_CFLAGS) \
$(AVAHI_CFLAGS) \
$(LIBWRAP_CFLAGS) \
$(SQLITE_CFLAGS)
src_mpd_LDADD = \
+ $(DB_LIBS) \
$(PLAYLIST_LIBS) \
$(AVAHI_LIBS) \
$(LIBWRAP_LDFLAGS) \
@@ -35,190 +41,42 @@ src_mpd_LDADD = \
$(FILTER_LIBS) \
$(ENCODER_LIBS) \
$(MIXER_LIBS) \
+ libconf.a \
+ libevent.a \
libutil.a \
+ libfs.a \
$(SYSTEMD_DAEMON_LIBS) \
$(GLIB_LIBS)
mpd_headers = \
src/check.h \
- src/notify.h \
src/ack.h \
- src/ape.h \
- src/audio_format.h \
- src/audio_check.h \
- src/audio_parser.h \
- src/output_internal.h \
- src/output_api.h \
- src/output_list.h \
- src/output_all.h \
- src/output_thread.h \
- src/output_control.h \
- src/output_state.h \
- src/output_print.h \
- src/output_command.h \
src/filter_internal.h \
- src/filter_config.h \
- src/filter_plugin.h \
- src/filter_registry.h \
- src/filter/autoconvert_filter_plugin.h \
- src/filter/chain_filter_plugin.h \
- src/filter/convert_filter_plugin.h \
- src/filter/replay_gain_filter_plugin.h \
- src/filter/volume_filter_plugin.h \
src/command.h \
- src/idle.h \
- src/cmdline.h \
src/conf.h \
- src/crossfade.h \
- src/dbUtils.h \
- src/decoder_thread.h \
- src/decoder_control.h \
- src/decoder_plugin.h \
- src/decoder_command.h \
- src/decoder_buffer.h \
- src/decoder_api.h \
- src/decoder_plugin.h \
- src/decoder_internal.h \
- src/directory.h \
- src/directory_save.h \
- src/database.h \
- src/encoder_plugin.h \
- src/encoder_list.h \
- src/encoder_api.h \
- src/exclude.h \
src/fd_util.h \
+ src/gerror.h \
src/glib_compat.h \
- src/update.h \
- src/inotify_source.h \
- src/inotify_queue.h \
- src/inotify_update.h \
src/gcc.h \
- src/decoder_list.h \
- src/decoder_print.h \
- src/decoder/flac_compat.h \
- src/decoder/flac_metadata.h \
- src/decoder/flac_pcm.h \
- src/decoder/_flac_common.h \
- src/decoder/_ogg_common.h \
- src/decoder/pcm_decoder_plugin.h \
- src/input_init.h \
- src/input_plugin.h \
- src/input_registry.h \
src/input_stream.h \
- src/input/file_input_plugin.h \
- src/input/ffmpeg_input_plugin.h \
- src/input/curl_input_plugin.h \
- src/input/rewind_input_plugin.h \
- src/input/mms_input_plugin.h \
- src/input/despotify_input_plugin.h \
- src/input/cdio_paranoia_input_plugin.h \
- src/despotify_utils.h \
- src/text_file.h \
- src/text_input_stream.h \
- src/icy_server.h \
- src/icy_metadata.h \
- src/client.h \
- src/client_internal.h \
- src/server_socket.h \
- src/listen.h \
- src/log.h \
+ src/TextInputStream.hxx \
src/ls.h \
- src/main.h \
- src/mixer_all.h \
- src/mixer_api.h \
- src/mixer_control.h \
- src/mixer_list.h \
- src/event_pipe.h \
src/mixer_plugin.h \
- src/mixer_type.h \
- src/mixer/software_mixer_plugin.h \
- src/mixer/pulse_mixer_plugin.h \
src/daemon.h \
src/AudioCompress/config.h \
src/AudioCompress/compress.h \
- src/buffer.h \
- src/pipe.h \
- src/chunk.h \
- src/path.h \
- src/mapper.h \
src/open.h \
- src/output/httpd_client.h \
- src/output/httpd_internal.h \
- src/page.h \
- src/permission.h \
- src/player_thread.h \
- src/player_control.h \
- src/playlist.h \
+ src/Playlist.hxx \
src/playlist_error.h \
- src/playlist_internal.h \
- src/playlist_print.h \
- src/playlist_save.h \
- src/playlist_state.h \
- src/playlist_plugin.h \
- src/playlist_list.h \
- src/playlist_mapper.h \
- src/playlist_any.h \
- src/playlist_song.h \
- src/playlist_queue.h \
- src/playlist_vector.h \
- src/playlist_database.h \
- src/playlist/extm3u_playlist_plugin.h \
- src/playlist/m3u_playlist_plugin.h \
- src/playlist/pls_playlist_plugin.h \
- src/playlist/xspf_playlist_plugin.h \
- src/playlist/asx_playlist_plugin.h \
- src/playlist/rss_playlist_plugin.h \
- src/playlist/lastfm_playlist_plugin.h \
- src/playlist/despotify_playlist_plugin.h \
- src/playlist/cue_playlist_plugin.h \
src/poison.h \
src/riff.h \
src/aiff.h \
- src/queue.h \
- src/queue_print.h \
- src/queue_save.h \
- src/refcount.h \
src/replay_gain_config.h \
src/replay_gain_info.h \
- src/replay_gain_ape.h \
- src/sig_handlers.h \
- src/song.h \
- src/song_print.h \
- src/song_save.h \
- src/song_sticker.h \
- src/song_sort.c src/song_sort.h \
- src/socket_util.h \
- src/state_file.h \
+ src/TimePrint.cxx src/TimePrint.hxx \
src/stats.h \
- src/sticker.h \
- src/sticker_print.h \
- src/tag.h \
src/tag_internal.h \
- src/tag_pool.h \
- src/tag_table.h \
- src/tag_ape.h \
- src/tag_id3.h \
- src/tag_rva2.h \
- src/tag_print.h \
- src/tag_save.h \
- src/tokenizer.h \
- src/strset.h \
- src/uri.h \
- src/utils.h \
- src/string_util.h \
- src/volume.h \
- src/zeroconf.h src/zeroconf-internal.h \
- src/locate.h \
- src/stored_playlist.h \
- src/timer.h \
- src/archive_api.h \
- src/archive_internal.h \
- src/archive_list.h \
- src/archive_plugin.h \
- src/archive/bz2_archive_plugin.h \
- src/archive/iso9660_archive_plugin.h \
- src/archive/zzip_archive_plugin.h \
- src/input/archive_input_plugin.h \
+ src/Timer.hxx \
src/mpd_error.h
src_mpd_SOURCES = \
@@ -226,134 +84,152 @@ src_mpd_SOURCES = \
$(DECODER_SRC) \
$(OUTPUT_API_SRC) \
$(MIXER_API_SRC) \
- src/glib_socket.h \
+ src/thread/Mutex.hxx \
+ src/thread/PosixMutex.hxx \
+ src/thread/CriticalSection.hxx \
+ src/thread/GLibMutex.hxx \
+ src/thread/Cond.hxx \
+ src/thread/PosixCond.hxx \
+ src/thread/WindowsCond.hxx \
+ src/thread/GLibCond.hxx \
src/clock.c src/clock.h \
- src/notify.c \
- src/audio_config.c src/audio_config.h \
- src/audio_check.c \
- src/audio_format.c \
- src/audio_parser.c \
- src/protocol/argparser.c src/protocol/argparser.h \
- src/protocol/result.c src/protocol/result.h \
- src/command.c \
- src/idle.c \
- src/cmdline.c \
- src/conf.c \
- src/crossfade.c \
- src/cue/cue_parser.c src/cue/cue_parser.h \
- src/dbUtils.c \
- src/decoder_thread.c \
- src/decoder_control.c \
- src/decoder_api.c \
- src/decoder_internal.c \
- src/decoder_print.c \
- src/directory.c \
- src/directory_save.c \
- src/database.c \
- src/db_internal.h \
+ src/notify.cxx src/notify.hxx \
+ src/AudioConfig.cxx src/AudioConfig.hxx \
+ src/CheckAudioFormat.cxx src/CheckAudioFormat.hxx \
+ src/AudioFormat.cxx src/AudioFormat.hxx \
+ src/AudioParser.cxx src/AudioParser.hxx \
+ src/protocol/ArgParser.cxx src/protocol/ArgParser.hxx \
+ src/protocol/Result.cxx src/protocol/Result.hxx \
+ src/CommandError.cxx src/CommandError.hxx \
+ src/AllCommands.cxx src/AllCommands.hxx \
+ src/QueueCommands.cxx src/QueueCommands.hxx \
+ src/PlayerCommands.cxx src/PlayerCommands.hxx \
+ src/PlaylistCommands.cxx src/PlaylistCommands.hxx \
+ src/DatabaseCommands.cxx src/DatabaseCommands.hxx \
+ src/OutputCommands.cxx src/OutputCommands.hxx \
+ src/MessageCommands.cxx src/MessageCommands.hxx \
+ src/OtherCommands.cxx src/OtherCommands.hxx \
+ src/Idle.cxx src/Idle.hxx \
+ src/CommandLine.cxx src/CommandLine.hxx \
+ src/CrossFade.cxx src/CrossFade.hxx \
+ src/cue/CueParser.cxx src/cue/CueParser.hxx \
+ src/DecoderError.hxx \
+ src/DecoderThread.cxx src/DecoderThread.hxx \
+ src/DecoderCommand.hxx \
+ src/DecoderControl.cxx src/DecoderControl.hxx \
+ src/DecoderAPI.cxx src/DecoderAPI.hxx \
+ src/DecoderPlugin.hxx \
+ src/DecoderInternal.cxx src/DecoderInternal.hxx \
+ src/DecoderPrint.cxx src/DecoderPrint.hxx \
+ src/Directory.cxx src/Directory.hxx \
+ src/DirectorySave.cxx src/DirectorySave.hxx \
+ src/DatabaseSimple.hxx \
+ src/DatabaseGlue.cxx src/DatabaseGlue.hxx \
+ src/DatabasePrint.cxx src/DatabasePrint.hxx \
+ src/DatabaseQueue.cxx src/DatabaseQueue.hxx \
+ src/DatabasePlaylist.cxx src/DatabasePlaylist.hxx \
src/db_error.h \
- src/db_lock.c src/db_lock.h \
- src/db_save.c src/db_save.h \
- src/db_print.c src/db_print.h \
- src/db_plugin.h \
- src/db_visitor.h \
- src/db_selection.h \
- src/db/simple_db_plugin.c src/db/simple_db_plugin.h \
- src/exclude.c \
+ src/DatabaseLock.cxx src/DatabaseLock.hxx \
+ src/DatabaseSave.cxx src/DatabaseSave.hxx \
+ src/DatabasePlugin.hxx \
+ src/DatabaseVisitor.hxx \
+ src/DatabaseSelection.cxx src/DatabaseSelection.hxx \
+ src/ExcludeList.cxx src/ExcludeList.hxx \
src/fd_util.c \
- src/fifo_buffer.c src/fifo_buffer.h \
- src/growing_fifo.c src/growing_fifo.h \
- src/filter_config.c \
- src/filter_plugin.c \
- src/filter_registry.c \
- src/update.c \
- src/update_queue.c src/update_queue.h \
- src/update_io.c src/update_io.h \
- src/update_db.c src/update_db.h \
- src/update_walk.c src/update_walk.h \
- src/update_song.c src/update_song.h \
- src/update_container.c src/update_container.h \
- src/update_internal.h \
- src/update_remove.c src/update_remove.h \
- src/client.c \
- src/client_event.c \
- src/client_expire.c \
- src/client_global.c \
- src/client_idle.h \
- src/client_idle.c \
- src/client_list.c \
- src/client_new.c \
- src/client_process.c \
- src/client_read.c \
- src/client_write.c \
- src/client_message.h \
- src/client_message.c \
- src/client_subscribe.h \
- src/client_subscribe.c \
- src/client_file.c src/client_file.h \
- src/server_socket.c \
- src/listen.c \
- src/log.c \
- src/ls.c \
- src/io_thread.c src/io_thread.h \
- src/main.c \
- src/main_win32.c \
- src/event_pipe.c \
+ src/FilterConfig.cxx src/FilterConfig.hxx \
+ src/FilterPlugin.cxx src/FilterPlugin.hxx \
+ src/FilterRegistry.cxx src/FilterRegistry.hxx \
+ src/UpdateGlue.cxx src/UpdateGlue.hxx \
+ src/UpdateQueue.cxx src/UpdateQueue.hxx \
+ src/UpdateIO.cxx src/UpdateIO.hxx \
+ src/UpdateDatabase.cxx src/UpdateDatabase.hxx \
+ src/UpdateWalk.cxx src/UpdateWalk.hxx \
+ src/UpdateSong.cxx src/UpdateSong.hxx \
+ src/UpdateContainer.cxx src/UpdateContainer.hxx \
+ src/UpdateInternal.hxx \
+ src/UpdateRemove.cxx src/UpdateRemove.hxx \
+ src/CommandListBuilder.cxx src/CommandListBuilder.hxx \
+ src/Client.cxx src/Client.hxx \
+ src/ClientInternal.hxx \
+ src/ClientEvent.cxx \
+ src/ClientExpire.cxx \
+ src/ClientGlobal.cxx \
+ src/ClientIdle.cxx \
+ src/ClientList.cxx src/ClientList.hxx \
+ src/ClientNew.cxx \
+ src/ClientProcess.cxx \
+ src/ClientRead.cxx \
+ src/ClientWrite.cxx \
+ src/ClientMessage.cxx src/ClientMessage.hxx \
+ src/ClientSubscribe.cxx src/ClientSubscribe.hxx \
+ src/ClientFile.cxx src/ClientFile.hxx \
+ src/Listen.cxx src/Listen.hxx \
+ src/Log.cxx src/Log.hxx \
+ src/ls.cxx \
+ src/SocketError.hxx \
+ src/io_error.h \
+ src/IOThread.cxx src/IOThread.hxx \
+ src/Main.cxx src/Main.hxx \
+ src/Instance.cxx src/Instance.hxx \
+ src/Win32Main.cxx \
+ src/GlobalEvents.cxx src/GlobalEvents.hxx \
src/daemon.c \
src/AudioCompress/compress.c \
- src/buffer.c \
- src/pipe.c \
- src/chunk.c \
- src/path.c \
- src/mapper.c \
- src/page.c \
- src/permission.c \
- src/player_thread.c \
- src/player_control.c \
- src/playlist.c \
- src/playlist_global.c \
- src/playlist_control.c \
- src/playlist_edit.c \
- src/playlist_print.c \
- src/playlist_save.c \
- src/playlist_mapper.c \
- src/playlist_any.c \
- src/playlist_song.c \
- src/playlist_state.c \
- src/playlist_queue.c \
- src/playlist_vector.c \
- src/playlist_database.c \
- src/queue.c \
- src/queue_print.c \
- src/queue_save.c \
- src/replay_gain_config.c \
- src/replay_gain_info.c \
- src/sig_handlers.c \
- src/song.c \
- src/song_update.c \
- src/song_print.c \
- src/song_save.c \
+ src/MusicBuffer.cxx src/MusicBuffer.hxx \
+ src/MusicPipe.cxx src/MusicPipe.hxx \
+ src/MusicChunk.cxx src/MusicChunk.hxx \
+ src/Mapper.cxx src/Mapper.hxx \
+ src/Page.cxx src/Page.hxx \
+ src/Partition.hxx \
+ src/Permission.cxx src/Permission.hxx \
+ src/PlayerThread.cxx src/PlayerThread.hxx \
+ src/PlayerControl.cxx src/PlayerControl.hxx \
+ src/Playlist.cxx \
+ src/PlaylistGlobal.cxx src/PlaylistGlobal.hxx \
+ src/PlaylistControl.cxx \
+ src/PlaylistEdit.cxx \
+ src/PlaylistPrint.cxx src/PlaylistPrint.hxx \
+ src/PlaylistSave.cxx src/PlaylistSave.hxx \
+ src/PlaylistMapper.cxx src/PlaylistMapper.hxx \
+ src/PlaylistAny.cxx src/PlaylistAny.hxx \
+ src/PlaylistSong.cxx src/PlaylistSong.hxx \
+ src/PlaylistState.cxx src/PlaylistState.hxx \
+ src/PlaylistQueue.cxx src/PlaylistQueue.hxx \
+ src/PlaylistVector.cxx src/PlaylistVector.hxx \
+ src/PlaylistInfo.hxx \
+ src/PlaylistDatabase.cxx \
+ src/IdTable.hxx \
+ src/Queue.cxx src/Queue.hxx \
+ src/QueuePrint.cxx src/QueuePrint.hxx \
+ src/QueueSave.cxx src/QueueSave.hxx \
+ src/ReplayGainConfig.cxx \
+ src/ReplayGainInfo.cxx \
+ src/SignalHandlers.cxx src/SignalHandlers.hxx \
+ src/Song.cxx src/Song.hxx \
+ src/SongUpdate.cxx \
+ src/SongPrint.cxx src/SongPrint.hxx \
+ src/SongSave.cxx src/SongSave.hxx \
+ src/SongSort.cxx src/SongSort.hxx \
src/resolver.c src/resolver.h \
- src/socket_util.c \
- src/state_file.c \
- src/stats.c \
- src/tag.c \
- src/tag_pool.c \
- src/tag_print.c \
- src/tag_save.c \
- src/tag_handler.c src/tag_handler.h \
- src/tag_file.c src/tag_file.h \
- src/tokenizer.c \
- src/text_file.c \
- src/text_input_stream.c \
- src/strset.c \
- src/uri.c \
- src/utils.c \
- src/string_util.c \
- src/volume.c \
- src/locate.c \
- src/stored_playlist.c \
- src/timer.c
+ src/SocketUtil.cxx src/SocketUtil.hxx \
+ src/StateFile.cxx src/StateFile.hxx \
+ src/Stats.cxx \
+ src/TagType.h \
+ src/Tag.cxx src/Tag.hxx \
+ src/TagTable.hxx \
+ src/TagNames.c \
+ src/TagPool.cxx src/TagPool.hxx \
+ src/TagPrint.cxx src/TagPrint.hxx \
+ src/TagSave.cxx src/TagSave.hxx \
+ src/TagHandler.cxx src/TagHandler.hxx \
+ src/TagFile.cxx src/TagFile.hxx \
+ src/TextFile.cxx src/TextFile.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
#
# Windows resource file
@@ -371,51 +247,75 @@ endif
if ENABLE_DESPOTIFY
src_mpd_SOURCES += \
- src/despotify_utils.c
+ src/DespotifyUtils.cxx src/DespotifyUtils.hxx
endif
if ENABLE_INOTIFY
src_mpd_SOURCES += \
- src/inotify_source.c \
- src/inotify_queue.c \
- src/inotify_update.c
+ src/InotifySource.cxx src/InotifySource.hxx \
+ src/InotifyQueue.cxx src/InotifyQueue.hxx \
+ src/InotifyUpdate.cxx src/InotifyUpdate.hxx
endif
if ENABLE_SQLITE
src_mpd_SOURCES += \
- src/sticker.c \
- src/sticker_print.c \
- src/song_sticker.c
+ src/StickerCommands.cxx src/StickerCommands.hxx \
+ src/StickerDatabase.cxx src/StickerDatabase.hxx \
+ src/StickerPrint.cxx src/StickerPrint.hxx \
+ src/SongSticker.cxx src/SongSticker.hxx
endif
# Generic utility library
libutil_a_SOURCES = \
+ src/util/StringUtil.cxx src/util/StringUtil.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/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/list.h \
src/util/list_sort.c src/util/list_sort.h \
src/util/byte_reverse.c src/util/byte_reverse.h \
src/util/bit_reverse.c src/util/bit_reverse.h
+# Event loop library
+
+libevent_a_SOURCES = \
+ src/event/WakeFD.cxx src/event/WakeFD.hxx \
+ src/event/TimeoutMonitor.hxx src/event/TimeoutMonitor.cxx \
+ src/event/SocketMonitor.cxx src/event/SocketMonitor.hxx \
+ src/event/BufferedSocket.cxx src/event/BufferedSocket.hxx \
+ src/event/FullyBufferedSocket.cxx src/event/FullyBufferedSocket.hxx \
+ src/event/MultiSocketMonitor.cxx src/event/MultiSocketMonitor.hxx \
+ src/event/ServerSocket.cxx src/event/ServerSocket.hxx \
+ src/event/Loop.hxx
+
# PCM library
libpcm_a_SOURCES = \
- src/pcm_buffer.c src/pcm_buffer.h \
- src/pcm_export.c src/pcm_export.h \
- src/pcm_convert.c src/pcm_convert.h \
- src/dsd2pcm/dsd2pcm.c src/dsd2pcm/dsd2pcm.h \
- src/pcm_dsd.c src/pcm_dsd.h \
- src/pcm_dsd_usb.c src/pcm_dsd_usb.h \
- src/pcm_volume.c src/pcm_volume.h \
- src/pcm_mix.c src/pcm_mix.h \
- src/pcm_channels.c src/pcm_channels.h \
- src/pcm_pack.c src/pcm_pack.h \
- src/pcm_format.c src/pcm_format.h \
- src/pcm_resample.c src/pcm_resample.h \
- src/pcm_resample_fallback.c \
- src/pcm_resample_internal.h \
- src/pcm_dither.c src/pcm_dither.h \
- src/pcm_prng.h \
- src/pcm_utils.h
+ 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/PcmMix.cxx src/pcm/PcmMix.hxx \
+ src/pcm/PcmChannels.cxx src/pcm/PcmChannels.hxx \
+ src/pcm/pcm_pack.c src/pcm/pcm_pack.h \
+ 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/PcmDither.cxx src/pcm/PcmDither.hxx \
+ src/pcm/PcmPrng.hxx \
+ src/pcm/PcmUtils.hxx
libpcm_a_CPPFLAGS = $(AM_CPPFLAGS) \
$(SAMPLERATE_CFLAGS)
@@ -424,9 +324,32 @@ PCM_LIBS = \
$(SAMPLERATE_LIBS)
if HAVE_LIBSAMPLERATE
-libpcm_a_SOURCES += src/pcm_resample_libsamplerate.c
+libpcm_a_SOURCES += src/pcm/PcmResampleLibsamplerate.cxx
endif
+# File system library
+
+libfs_a_SOURCES = \
+ src/fs/Path.cxx src/fs/Path.hxx \
+ src/fs/FileSystem.cxx src/fs/FileSystem.hxx \
+ src/fs/DirectoryReader.hxx
+
+# database plugins
+
+libdb_plugins_a_SOURCES = \
+ src/DatabaseRegistry.cxx src/DatabaseRegistry.hxx \
+ src/DatabaseHelpers.cxx src/DatabaseHelpers.hxx \
+ src/db/SimpleDatabasePlugin.cxx src/db/SimpleDatabasePlugin.hxx
+
+if HAVE_LIBMPDCLIENT
+libdb_plugins_a_SOURCES += \
+ src/db/ProxyDatabasePlugin.cxx src/db/ProxyDatabasePlugin.hxx
+endif
+
+DB_LIBS = \
+ libdb_plugins.a \
+ $(LIBMPDCLIENT_LIBS)
+
# archive plugins
if ENABLE_ARCHIVE
@@ -434,13 +357,15 @@ if ENABLE_ARCHIVE
noinst_LIBRARIES += libarchive.a
src_mpd_SOURCES += \
- src/update_archive.c src/update_archive.h
+ src/UpdateArchive.cxx src/UpdateArchive.hxx
libarchive_a_SOURCES = \
- src/archive_api.c \
- src/archive_list.c \
- src/archive_plugin.c \
- src/input/archive_input_plugin.c
+ src/ArchiveLookup.cxx src/ArchiveLookup.hxx \
+ src/ArchiveList.cxx src/ArchiveList.hxx \
+ src/ArchivePlugin.cxx src/ArchivePlugin.hxx \
+ src/ArchiveVisitor.hxx \
+ src/ArchiveFile.hxx \
+ src/input/ArchiveInputPlugin.cxx src/input/ArchiveInputPlugin.hxx
libarchive_a_CPPFLAGS = $(AM_CPPFLAGS) \
$(BZ2_CFLAGS) \
$(ISO9660_CFLAGS) \
@@ -453,21 +378,38 @@ ARCHIVE_LIBS = \
$(ZZIP_LIBS)
if HAVE_BZ2
-libarchive_a_SOURCES += src/archive/bz2_archive_plugin.c
+libarchive_a_SOURCES += \
+ src/archive/Bzip2ArchivePlugin.cxx \
+ src/archive/Bzip2ArchivePlugin.hxx
endif
if HAVE_ZZIP
-libarchive_a_SOURCES += src/archive/zzip_archive_plugin.c
+libarchive_a_SOURCES += \
+ src/archive/ZzipArchivePlugin.cxx \
+ src/archive/ZzipArchivePlugin.hxx
endif
if HAVE_ISO9660
-libarchive_a_SOURCES += src/archive/iso9660_archive_plugin.c
+libarchive_a_SOURCES += \
+ src/archive/Iso9660ArchivePlugin.cxx \
+ src/archive/Iso9660ArchivePlugin.hxx
endif
else
ARCHIVE_LIBS =
endif
+# configuration library
+
+libconf_a_SOURCES = \
+ src/ConfigPath.cxx src/ConfigPath.hxx \
+ src/ConfigData.cxx src/ConfigData.hxx \
+ src/ConfigParser.cxx src/ConfigParser.hxx \
+ src/ConfigGlobal.cxx src/ConfigGlobal.hxx \
+ src/ConfigFile.cxx src/ConfigFile.hxx \
+ src/ConfigTemplates.cxx src/ConfigTemplates.hxx \
+ src/ConfigQuark.hxx \
+ src/ConfigOption.hxx
# tag plugins
@@ -478,30 +420,31 @@ TAG_LIBS = \
$(ID3TAG_LIBS)
libtag_a_SOURCES =\
- src/ape.c \
- src/replay_gain_ape.c \
- src/tag_ape.c
+ src/ApeLoader.cxx src/ApeLoader.hxx \
+ src/ApeReplayGain.cxx src/ApeReplayGain.hxx \
+ src/ApeTag.cxx src/ApeTag.hxx
if HAVE_ID3TAG
libtag_a_SOURCES += \
- src/tag_id3.c \
- src/tag_rva2.c \
+ src/TagId3.cxx src/TagId3.hxx \
+ src/TagRva2.cxx src/TagRva2.hxx \
src/riff.c src/aiff.c
endif
# decoder plugins
libdecoder_plugins_a_SOURCES = \
- src/decoder/pcm_decoder_plugin.c \
- src/decoder/dsdiff_decoder_plugin.c \
- src/decoder/dsdiff_decoder_plugin.h \
- src/decoder/dsf_decoder_plugin.c \
- src/decoder/dsf_decoder_plugin.h \
- src/decoder/dsdlib.c \
- src/decoder/dsdlib.h \
- src/decoder_buffer.c \
- src/decoder_plugin.c \
- src/decoder_list.c
+ src/decoder/PcmDecoderPlugin.cxx \
+ src/decoder/PcmDecoderPlugin.hxx \
+ src/decoder/DsdiffDecoderPlugin.cxx \
+ src/decoder/DsdiffDecoderPlugin.hxx \
+ src/decoder/DsfDecoderPlugin.cxx \
+ src/decoder/DsfDecoderPlugin.hxx \
+ src/decoder/DsdLib.cxx \
+ src/decoder/DsdLib.hxx \
+ src/DecoderBuffer.cxx src/DecoderBuffer.hxx \
+ src/DecoderPlugin.cxx \
+ src/DecoderList.cxx src/DecoderList.hxx
libdecoder_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \
$(VORBIS_CFLAGS) $(TREMOR_CFLAGS) \
$(patsubst -I%/FLAC,-I%,$(FLAC_CFLAGS)) \
@@ -515,8 +458,10 @@ libdecoder_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \
$(WAVPACK_CFLAGS) \
$(MAD_CFLAGS) \
$(MPG123_CFLAGS) \
+ $(OPUS_CFLAGS) \
$(FFMPEG_CFLAGS) \
$(MPCDEC_CFLAGS) \
+ $(ADPLUG_CFLAGS) \
$(FAAD_CFLAGS)
DECODER_LIBS = \
@@ -532,70 +477,103 @@ DECODER_LIBS = \
$(WAVPACK_LIBS) \
$(MAD_LIBS) \
$(MPG123_LIBS) \
- $(MP4FF_LIBS) \
+ $(OPUS_LIBS) \
$(FFMPEG_LIBS) \
$(MPCDEC_LIBS) \
+ $(ADPLUG_LIBS) \
$(FAAD_LIBS)
DECODER_SRC =
if HAVE_MAD
-libdecoder_plugins_a_SOURCES += src/decoder/mad_decoder_plugin.c
+libdecoder_plugins_a_SOURCES += \
+ src/decoder/MadDecoderPlugin.cxx \
+ src/decoder/MadDecoderPlugin.hxx
endif
if HAVE_MPG123
-libdecoder_plugins_a_SOURCES += src/decoder/mpg123_decoder_plugin.c
+libdecoder_plugins_a_SOURCES += \
+ src/decoder/Mpg123DecoderPlugin.cxx \
+ src/decoder/Mpg123DecoderPlugin.hxx
endif
if HAVE_MPCDEC
-libdecoder_plugins_a_SOURCES += src/decoder/mpcdec_decoder_plugin.c
+libdecoder_plugins_a_SOURCES += \
+ src/decoder/MpcdecDecoderPlugin.cxx \
+ src/decoder/MpcdecDecoderPlugin.hxx
endif
-if HAVE_WAVPACK
-libdecoder_plugins_a_SOURCES += src/decoder/wavpack_decoder_plugin.c
+if HAVE_OPUS
+libdecoder_plugins_a_SOURCES += \
+ src/decoder/OggUtil.cxx \
+ src/decoder/OggUtil.hxx \
+ src/decoder/OggFind.cxx src/decoder/OggFind.hxx \
+ src/decoder/OpusReader.hxx \
+ src/decoder/OpusHead.hxx \
+ src/decoder/OpusHead.cxx \
+ src/decoder/OpusTags.cxx \
+ src/decoder/OpusTags.hxx \
+ src/decoder/OpusDecoderPlugin.cxx \
+ src/decoder/OpusDecoderPlugin.h
endif
-if HAVE_FAAD
-libdecoder_plugins_a_SOURCES += src/decoder/faad_decoder_plugin.c
+if HAVE_WAVPACK
+libdecoder_plugins_a_SOURCES += \
+ src/decoder/WavpackDecoderPlugin.cxx \
+ src/decoder/WavpackDecoderPlugin.hxx
endif
-if HAVE_MP4
-libdecoder_plugins_a_SOURCES += src/decoder/mp4ff_decoder_plugin.c
+if HAVE_ADPLUG
+libdecoder_plugins_a_SOURCES += \
+ src/decoder/AdPlugDecoderPlugin.cxx \
+ src/decoder/AdPlugDecoderPlugin.h
endif
-if HAVE_OGG_COMMON
-libdecoder_plugins_a_SOURCES += src/decoder/_ogg_common.c
+if HAVE_FAAD
+libdecoder_plugins_a_SOURCES += \
+ src/decoder/FaadDecoderPlugin.cxx src/decoder/FaadDecoderPlugin.hxx
endif
-if HAVE_FLAC_COMMON
+if HAVE_XIPH
libdecoder_plugins_a_SOURCES += \
- src/decoder/flac_metadata.c \
- src/decoder/flac_pcm.c \
- src/decoder/_flac_common.c
+ src/decoder/XiphTags.cxx src/decoder/XiphTags.hxx \
+ src/decoder/OggCodec.cxx src/decoder/OggCodec.hxx
endif
if ENABLE_VORBIS_DECODER
libdecoder_plugins_a_SOURCES += \
- src/decoder/vorbis_comments.c \
- src/decoder/vorbis_comments.h \
- src/decoder/vorbis_decoder_plugin.c
+ src/decoder/VorbisComments.cxx src/decoder/VorbisComments.hxx \
+ src/decoder/VorbisDecoderPlugin.cxx src/decoder/VorbisDecoderPlugin.h
endif
if HAVE_FLAC
-libdecoder_plugins_a_SOURCES += src/decoder/flac_decoder_plugin.c
+libdecoder_plugins_a_SOURCES += \
+ src/decoder/FlacInput.cxx src/decoder/FlacInput.hxx \
+ src/decoder/FlacIOHandle.cxx src/decoder/FlacIOHandle.hxx \
+ src/decoder/FlacMetadata.cxx src/decoder/FlacMetadata.hxx \
+ src/decoder/FlacPcm.cxx src/decoder/FlacPcm.hxx \
+ src/decoder/FlacCommon.cxx src/decoder/FlacCommon.hxx \
+ src/decoder/FlacDecoderPlugin.cxx \
+ src/decoder/FlacDecoderPlugin.h
endif
if HAVE_AUDIOFILE
-libdecoder_plugins_a_SOURCES += src/decoder/audiofile_decoder_plugin.c
+libdecoder_plugins_a_SOURCES += \
+ src/decoder/AudiofileDecoderPlugin.cxx \
+ src/decoder/AudiofileDecoderPlugin.hxx
endif
if ENABLE_MIKMOD_DECODER
-libdecoder_plugins_a_SOURCES += src/decoder/mikmod_decoder_plugin.c
+libdecoder_plugins_a_SOURCES += \
+ src/decoder/MikmodDecoderPlugin.cxx \
+ src/decoder/MikmodDecoderPlugin.hxx
endif
if HAVE_MODPLUG
-libmodplug_decoder_plugin_a_SOURCES = src/decoder/modplug_decoder_plugin.c
-libmodplug_decoder_plugin_a_CFLAGS = $(src_mpd_CFLAGS) $(MODPLUG_CFLAGS)
+libmodplug_decoder_plugin_a_SOURCES = \
+ src/decoder/ModplugDecoderPlugin.cxx \
+ src/decoder/ModplugDecoderPlugin.hxx
+libmodplug_decoder_plugin_a_CXXFLAGS = $(AM_CXXFLAGS) $(MODPLUG_CFLAGS)
libmodplug_decoder_plugin_a_CPPFLAGS = $(src_mpd_CPPFLAGS)
noinst_LIBRARIES += libmodplug_decoder_plugin.a
DECODER_LIBS += libmodplug_decoder_plugin.a $(MODPLUG_LIBS)
@@ -603,30 +581,37 @@ endif
if ENABLE_SIDPLAY
libdecoder_plugins_a_SOURCES += src/decoder/sidplay_decoder_plugin.cxx
-DECODER_SRC += src/dummy.cxx
endif
if ENABLE_FLUIDSYNTH
-libdecoder_plugins_a_SOURCES += src/decoder/fluidsynth_decoder_plugin.c
+libdecoder_plugins_a_SOURCES += \
+ src/decoder/FluidsynthDecoderPlugin.cxx \
+ src/decoder/FluidsynthDecoderPlugin.hxx
endif
if ENABLE_WILDMIDI
-libdecoder_plugins_a_SOURCES += src/decoder/wildmidi_decoder_plugin.c
+libdecoder_plugins_a_SOURCES += \
+ src/decoder/WildmidiDecoderPlugin.cxx \
+ src/decoder/WildmidiDecoderPlugin.hxx
endif
if HAVE_FFMPEG
libdecoder_plugins_a_SOURCES += \
- src/decoder/ffmpeg_metadata.c \
- src/decoder/ffmpeg_metadata.h \
- src/decoder/ffmpeg_decoder_plugin.c
+ src/decoder/FfmpegMetaData.cxx \
+ src/decoder/FfmpegMetaData.hxx \
+ src/decoder/FfmpegDecoderPlugin.cxx \
+ src/decoder/FfmpegDecoderPlugin.hxx
endif
if ENABLE_SNDFILE
-libdecoder_plugins_a_SOURCES += src/decoder/sndfile_decoder_plugin.c
+libdecoder_plugins_a_SOURCES += \
+ src/decoder/SndfileDecoderPlugin.cxx \
+ src/decoder/SndfileDecoderPlugin.hxx
endif
if HAVE_GME
-libdecoder_plugins_a_SOURCES += src/decoder/gme_decoder_plugin.c
+libdecoder_plugins_a_SOURCES += \
+ src/decoder/GmeDecoderPlugin.cxx src/decoder/GmeDecoderPlugin.hxx
endif
# encoder plugins
@@ -639,6 +624,7 @@ libencoder_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \
$(LAME_CFLAGS) \
$(TWOLAME_CFLAGS) \
$(patsubst -I%/FLAC,-I%,$(FLAC_CFLAGS)) \
+ $(OPUS_CFLAGS) \
$(VORBISENC_CFLAGS)
ENCODER_LIBS = \
@@ -646,31 +632,49 @@ ENCODER_LIBS = \
$(LAME_LIBS) \
$(TWOLAME_LIBS) \
$(FLAC_LIBS) \
+ $(OPUS_LIBS) \
$(VORBISENC_LIBS)
-libencoder_plugins_a_SOURCES =
-
-libencoder_plugins_a_SOURCES += src/encoder_list.c
-libencoder_plugins_a_SOURCES += src/encoder/null_encoder.c
+libencoder_plugins_a_SOURCES = \
+ src/EncoderAPI.hxx \
+ src/EncoderPlugin.hxx \
+ src/encoder/OggStream.hxx \
+ src/encoder/NullEncoderPlugin.cxx src/encoder/NullEncoderPlugin.hxx \
+ src/EncoderList.cxx src/EncoderList.hxx
if ENABLE_WAVE_ENCODER
-libencoder_plugins_a_SOURCES += src/encoder/wave_encoder.c
+libencoder_plugins_a_SOURCES += \
+ src/encoder/WaveEncoderPlugin.cxx \
+ src/encoder/WaveEncoderPlugin.hxx
endif
if ENABLE_VORBIS_ENCODER
-libencoder_plugins_a_SOURCES += src/encoder/vorbis_encoder.c
+libencoder_plugins_a_SOURCES += \
+ src/encoder/VorbisEncoderPlugin.cxx \
+ src/encoder/VorbisEncoderPlugin.hxx
+endif
+
+if HAVE_OPUS
+libencoder_plugins_a_SOURCES += \
+ src/encoder/OpusEncoderPlugin.cxx \
+ src/encoder/OpusEncoderPlugin.hxx
endif
if ENABLE_LAME_ENCODER
-libencoder_plugins_a_SOURCES += src/encoder/lame_encoder.c
+libencoder_plugins_a_SOURCES += \
+ src/encoder/LameEncoderPlugin.cxx \
+ src/encoder/LameEncoderPlugin.hxx
endif
if ENABLE_TWOLAME_ENCODER
-libencoder_plugins_a_SOURCES += src/encoder/twolame_encoder.c
+libencoder_plugins_a_SOURCES += \
+ src/encoder/TwolameEncoderPlugin.cxx \
+ src/encoder/TwolameEncoderPlugin.hxx
endif
if ENABLE_FLAC_ENCODER
-libencoder_plugins_a_SOURCES += src/encoder/flac_encoder.c
+libencoder_plugins_a_SOURCES += \
+ src/encoder/FlacEncoderPlugin.cxx src/encoder/FlacEncoderPlugin.hxx
endif
else
@@ -679,14 +683,16 @@ endif
if HAVE_ZEROCONF
-src_mpd_SOURCES += src/zeroconf.c
+src_mpd_SOURCES += \
+ src/ZeroconfInternal.hxx \
+ src/ZeroconfGlue.cxx src/ZeroconfGlue.hxx
if HAVE_AVAHI
-src_mpd_SOURCES += src/zeroconf-avahi.c
+src_mpd_SOURCES += src/ZeroconfAvahi.cxx src/ZeroconfAvahi.hxx
endif
if HAVE_BONJOUR
-src_mpd_SOURCES += src/zeroconf-bonjour.c
+src_mpd_SOURCES += src/ZeroconfBonjour.cxx src/ZeroconfBonjour.hxx
endif
endif
@@ -695,16 +701,16 @@ endif
#
libinput_a_SOURCES = \
- src/input_init.c \
- src/input_registry.c \
- src/input_stream.c \
- src/input_internal.c src/input_internal.h \
- src/input/rewind_input_plugin.c \
- src/input/file_input_plugin.c
+ src/InputInit.cxx src/InputInit.hxx \
+ src/InputRegistry.cxx src/InputRegistry.hxx \
+ src/InputStream.cxx src/InputStream.hxx \
+ src/InputPlugin.hxx \
+ src/InputInternal.cxx src/InputInternal.hxx \
+ src/input/RewindInputPlugin.cxx src/input/RewindInputPlugin.hxx \
+ src/input/FileInputPlugin.cxx src/input/FileInputPlugin.hxx
libinput_a_CPPFLAGS = $(AM_CPPFLAGS) \
$(CURL_CFLAGS) \
- $(SOUP_CFLAGS) \
$(CDIO_PARANOIA_CFLAGS) \
$(FFMPEG_CFLAGS) \
$(DESPOTIFY_CFLAGS) \
@@ -713,44 +719,43 @@ libinput_a_CPPFLAGS = $(AM_CPPFLAGS) \
INPUT_LIBS = \
libinput.a \
$(CURL_LIBS) \
- $(SOUP_LIBS) \
$(CDIO_PARANOIA_LIBS) \
$(FFMPEG_LIBS) \
$(DESPOTIFY_LIBS) \
$(MMS_LIBS)
if ENABLE_CURL
-libinput_a_SOURCES += src/input/curl_input_plugin.c \
- src/icy_metadata.c
-endif
-
-if ENABLE_SOUP
libinput_a_SOURCES += \
- src/input/soup_input_plugin.c \
- src/input/soup_input_plugin.h
+ src/input/CurlInputPlugin.cxx src/input/CurlInputPlugin.hxx \
+ src/IcyMetaDataParser.cxx src/IcyMetaDataParser.hxx
endif
if ENABLE_CDIO_PARANOIA
-libinput_a_SOURCES += src/input/cdio_paranoia_input_plugin.c
+libinput_a_SOURCES += \
+ src/input/CdioParanoiaInputPlugin.cxx \
+ src/input/CdioParanoiaInputPlugin.hxx
endif
if HAVE_FFMPEG
-libinput_a_SOURCES += src/input/ffmpeg_input_plugin.c
+libinput_a_SOURCES += \
+ src/input/FfmpegInputPlugin.cxx src/input/FfmpegInputPlugin.hxx
endif
if ENABLE_MMS
-libinput_a_SOURCES += src/input/mms_input_plugin.c
+libinput_a_SOURCES += \
+ src/input/MmsInputPlugin.cxx src/input/MmsInputPlugin.hxx
endif
if ENABLE_DESPOTIFY
-libinput_a_SOURCES += src/input/despotify_input_plugin.c
+libinput_a_SOURCES += \
+ src/input/DespotifyInputPlugin.cxx \
+ src/input/DespotifyInputPlugin.hxx
endif
liboutput_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \
$(AO_CFLAGS) \
$(ALSA_CFLAGS) \
- $(FFADO_CFLAGS) \
$(JACK_CFLAGS) \
$(OPENAL_CFLAGS) \
$(OPENSSL_CFLAGS) \
@@ -763,134 +768,133 @@ OUTPUT_LIBS = \
$(AO_LIBS) \
$(ALSA_LIBS) \
$(ROAR_LIBS) \
- $(FFADO_LIBS) \
$(JACK_LIBS) \
$(OPENAL_LIBS) \
$(PULSE_LIBS) \
$(SHOUT_LIBS)
OUTPUT_API_SRC = \
- src/output_list.c \
- src/output_all.c \
- src/output_thread.c \
- src/output_control.c \
- src/output_state.c \
- src/output_print.c \
- src/output_command.c \
- src/output_plugin.c src/output_plugin.h \
- src/output_finish.c \
- src/output_init.c
+ src/OutputAPI.hxx \
+ src/OutputInternal.hxx \
+ src/OutputList.cxx src/OutputList.hxx \
+ src/OutputAll.cxx src/OutputAll.hxx \
+ src/OutputThread.cxx src/OutputThread.hxx \
+ src/OutputError.hxx \
+ src/OutputControl.cxx src/OutputControl.hxx \
+ src/OutputState.cxx src/OutputState.hxx \
+ src/OutputPrint.cxx src/OutputPrint.hxx \
+ src/OutputCommand.cxx src/OutputCommand.hxx \
+ src/OutputPlugin.cxx src/OutputPlugin.hxx \
+ src/OutputFinish.cxx \
+ src/OutputInit.cxx
liboutput_plugins_a_SOURCES = \
- src/output/null_output_plugin.h \
- src/output/null_output_plugin.c
+ src/output/NullOutputPlugin.cxx \
+ src/output/NullOutputPlugin.hxx
MIXER_LIBS = \
libmixer_plugins.a \
$(PULSE_LIBS)
MIXER_API_SRC = \
- src/mixer_control.c \
- src/mixer_type.c \
- src/mixer_all.c \
- src/mixer_api.c
+ src/MixerList.hxx \
+ src/MixerControl.cxx src/MixerControl.hxx \
+ src/MixerType.cxx src/MixerType.hxx \
+ src/MixerAll.cxx src/MixerAll.hxx \
+ src/MixerInternal.hxx
libmixer_plugins_a_SOURCES = \
- src/mixer/software_mixer_plugin.c
+ src/mixer/SoftwareMixerPlugin.cxx \
+ src/mixer/SoftwareMixerPlugin.hxx
libmixer_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \
$(ALSA_CFLAGS) \
$(PULSE_CFLAGS)
if HAVE_ALSA
liboutput_plugins_a_SOURCES += \
- src/output/alsa_output_plugin.c src/output/alsa_output_plugin.h
-libmixer_plugins_a_SOURCES += src/mixer/alsa_mixer_plugin.c
+ src/output/AlsaOutputPlugin.cxx \
+ src/output/AlsaOutputPlugin.hxx
+libmixer_plugins_a_SOURCES += src/mixer/AlsaMixerPlugin.cxx
endif
if HAVE_ROAR
liboutput_plugins_a_SOURCES += \
- src/output/roar_output_plugin.c src/output/roar_output_plugin.h
-libmixer_plugins_a_SOURCES += src/mixer/roar_mixer_plugin.c
-endif
-
-if ENABLE_FFADO_OUTPUT
-liboutput_plugins_a_SOURCES += \
- src/output/ffado_output_plugin.c src/output/ffado_output_plugin.h
+ src/output/RoarOutputPlugin.cxx src/output/RoarOutputPlugin.hxx
+libmixer_plugins_a_SOURCES += src/mixer/RoarMixerPlugin.cxx
endif
if HAVE_AO
liboutput_plugins_a_SOURCES += \
- src/output/ao_output_plugin.c src/output/ao_output_plugin.h
+ src/output/AoOutputPlugin.cxx src/output/AoOutputPlugin.hxx
endif
if HAVE_FIFO
liboutput_plugins_a_SOURCES += \
- src/output/fifo_output_plugin.c src/output/fifo_output_plugin.h
+ src/output/FifoOutputPlugin.cxx src/output/FifoOutputPlugin.hxx
endif
if ENABLE_PIPE_OUTPUT
liboutput_plugins_a_SOURCES += \
- src/output/pipe_output_plugin.c src/output/pipe_output_plugin.h
+ src/output/PipeOutputPlugin.cxx src/output/PipeOutputPlugin.hxx
endif
if HAVE_JACK
liboutput_plugins_a_SOURCES += \
- src/output/jack_output_plugin.c src/output/jack_output_plugin.h
-endif
-
-if HAVE_MVP
-liboutput_plugins_a_SOURCES += \
- src/output/mvp_output_plugin.c src/output/mvp_output_plugin.h
+ src/output/JackOutputPlugin.cxx src/output/JackOutputPlugin.hxx
endif
if HAVE_OSS
liboutput_plugins_a_SOURCES += \
- src/output/oss_output_plugin.c src/output/oss_output_plugin.h
-libmixer_plugins_a_SOURCES += src/mixer/oss_mixer_plugin.c
+ src/output/OssOutputPlugin.cxx \
+ src/output/OssOutputPlugin.hxx
+libmixer_plugins_a_SOURCES += src/mixer/OssMixerPlugin.cxx
endif
if HAVE_OPENAL
liboutput_plugins_a_SOURCES += \
- src/output/openal_output_plugin.c src/output/openal_output_plugin.h
+ src/output/OpenALOutputPlugin.cxx src/output/OpenALOutputPlugin.hxx
endif
if HAVE_OSX
liboutput_plugins_a_SOURCES += \
- src/output/osx_output_plugin.c src/output/osx_output_plugin.h
+ src/output/OSXOutputPlugin.cxx \
+ src/output/OSXOutputPlugin.hxx
endif
if HAVE_PULSE
liboutput_plugins_a_SOURCES += \
- src/output/pulse_output_plugin.c src/output/pulse_output_plugin.h
-libmixer_plugins_a_SOURCES += src/mixer/pulse_mixer_plugin.c
+ src/output/PulseOutputPlugin.cxx src/output/PulseOutputPlugin.hxx
+libmixer_plugins_a_SOURCES += \
+ src/mixer/PulseMixerPlugin.cxx src/mixer/PulseMixerPlugin.hxx
endif
if HAVE_SHOUT
liboutput_plugins_a_SOURCES += \
- src/output/shout_output_plugin.c src/output/shout_output_plugin.h
+ src/output/ShoutOutputPlugin.cxx src/output/ShoutOutputPlugin.hxx
endif
if ENABLE_RECORDER_OUTPUT
liboutput_plugins_a_SOURCES += \
- src/output/recorder_output_plugin.c src/output/recorder_output_plugin.h
+ src/output/RecorderOutputPlugin.cxx src/output/RecorderOutputPlugin.hxx
endif
if ENABLE_HTTPD_OUTPUT
liboutput_plugins_a_SOURCES += \
- src/icy_server.c \
- src/output/httpd_client.c \
- src/output/httpd_output_plugin.c src/output/httpd_output_plugin.h
+ src/IcyMetaDataServer.cxx src/IcyMetaDataServer.hxx \
+ src/output/HttpdInternal.hxx \
+ src/output/HttpdClient.cxx src/output/HttpdClient.hxx \
+ src/output/HttpdOutputPlugin.cxx src/output/HttpdOutputPlugin.hxx
endif
if ENABLE_SOLARIS_OUTPUT
liboutput_plugins_a_SOURCES += \
- src/output/solaris_output_plugin.c src/output/solaris_output_plugin.h
+ src/output/SolarisOutputPlugin.cxx src/output/SolarisOutputPlugin.hxx
endif
if ENABLE_WINMM_OUTPUT
liboutput_plugins_a_SOURCES += \
- src/output/winmm_output_plugin.c src/output/winmm_output_plugin.h
-libmixer_plugins_a_SOURCES += src/mixer/winmm_mixer_plugin.c
+ src/output/WinmmOutputPlugin.cxx src/output/WinmmOutputPlugin.hxx
+libmixer_plugins_a_SOURCES += src/mixer/WinmmMixerPlugin.cxx
endif
@@ -899,16 +903,26 @@ endif
#
libplaylist_plugins_a_SOURCES = \
- src/playlist/extm3u_playlist_plugin.c \
- src/playlist/m3u_playlist_plugin.c \
- src/playlist/pls_playlist_plugin.c \
- src/playlist/xspf_playlist_plugin.c \
- src/playlist/asx_playlist_plugin.c \
- src/playlist/rss_playlist_plugin.c \
- src/playlist/cue_playlist_plugin.c \
- src/playlist/embcue_playlist_plugin.c \
- src/playlist/embcue_playlist_plugin.h \
- src/playlist_list.c
+ src/PlaylistPlugin.hxx \
+ src/playlist/MemoryPlaylistProvider.cxx \
+ src/playlist/MemoryPlaylistProvider.hxx \
+ src/playlist/ExtM3uPlaylistPlugin.cxx \
+ 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) \
$(YAJL_CFLAGS) \
$(patsubst -I%/FLAC,-I%,$(FLAC_CFLAGS))
@@ -918,17 +932,21 @@ PLAYLIST_LIBS = \
$(FLAC_LIBS)
if ENABLE_LASTFM
-libplaylist_plugins_a_SOURCES += src/playlist/lastfm_playlist_plugin.c
+libplaylist_plugins_a_SOURCES += \
+ src/playlist/LastFMPlaylistPlugin.cxx \
+ src/playlist/LastFMPlaylistPlugin.hxx
endif
if ENABLE_DESPOTIFY
-libplaylist_plugins_a_SOURCES += src/playlist/despotify_playlist_plugin.c
+libplaylist_plugins_a_SOURCES += \
+ src/playlist/DespotifyPlaylistPlugin.cxx \
+ src/playlist/DespotifyPlaylistPlugin.hxx
endif
if ENABLE_SOUNDCLOUD
libplaylist_plugins_a_SOURCES += \
- src/playlist/soundcloud_playlist_plugin.h \
- src/playlist/soundcloud_playlist_plugin.c
+ src/playlist/SoundCloudPlaylistPlugin.cxx \
+ src/playlist/SoundCloudPlaylistPlugin.hxx
PLAYLIST_LIBS += $(YAJL_LIBS)
endif
@@ -937,14 +955,19 @@ endif
#
libfilter_plugins_a_SOURCES = \
- src/filter/null_filter_plugin.c \
- src/filter/chain_filter_plugin.c \
- src/filter/autoconvert_filter_plugin.c \
- src/filter/convert_filter_plugin.c \
- src/filter/route_filter_plugin.c \
- src/filter/normalize_filter_plugin.c \
- src/filter/replay_gain_filter_plugin.c \
- src/filter/volume_filter_plugin.c
+ src/filter/NullFilterPlugin.cxx \
+ src/filter/ChainFilterPlugin.cxx \
+ src/filter/ChainFilterPlugin.hxx \
+ src/filter/AutoConvertFilterPlugin.cxx \
+ src/filter/AutoConvertFilterPlugin.hxx \
+ src/filter/ConvertFilterPlugin.cxx \
+ src/filter/ConvertFilterPlugin.hxx \
+ src/filter/RouteFilterPlugin.cxx \
+ src/filter/NormalizeFilterPlugin.cxx \
+ src/filter/ReplayGainFilterPlugin.cxx \
+ src/filter/ReplayGainFilterPlugin.hxx \
+ src/filter/VolumeFilterPlugin.cxx \
+ src/filter/VolumeFilterPlugin.hxx
FILTER_LIBS = \
libfilter_plugins.a \
@@ -998,6 +1021,7 @@ noinst_PROGRAMS = \
$(C_TESTS) \
test/read_conf \
test/run_resolver \
+ test/DumpDatabase \
test/run_input \
test/dump_text_file \
test/dump_playlist \
@@ -1009,6 +1033,10 @@ noinst_PROGRAMS = \
test/run_normalize \
test/software_volume
+if ENABLE_ARCHIVE
+noinst_PROGRAMS += test/visit_archive
+endif
+
if HAVE_ID3TAG
noinst_PROGRAMS += test/dump_rva2
endif
@@ -1019,36 +1047,83 @@ noinst_PROGRAMS += test/read_mixer
endif
test_read_conf_LDADD = \
+ libconf.a \
+ libutil.a \
+ libfs.a \
$(GLIB_LIBS)
-test_read_conf_SOURCES = test/read_conf.c \
- src/conf.c src/tokenizer.c src/utils.c src/string_util.c
+test_read_conf_SOURCES = test/read_conf.cxx
test_run_resolver_LDADD = \
$(GLIB_LIBS)
test_run_resolver_SOURCES = test/run_resolver.c \
src/resolver.c
+test_DumpDatabase_LDADD = \
+ $(DB_LIBS) \
+ libconf.a \
+ libutil.a \
+ libfs.a \
+ $(GLIB_LIBS)
+test_DumpDatabase_SOURCES = test/DumpDatabase.cxx \
+ src/DatabaseRegistry.cxx \
+ src/DatabaseSelection.cxx \
+ src/Directory.cxx src/DirectorySave.cxx \
+ src/PlaylistVector.cxx src/PlaylistDatabase.cxx \
+ src/DatabaseLock.cxx src/DatabaseSave.cxx \
+ src/Song.cxx src/SongSave.cxx src/SongSort.cxx \
+ src/Tag.cxx src/TagNames.c src/TagPool.cxx src/TagSave.cxx \
+ src/SongFilter.cxx \
+ src/TextFile.cxx
+
test_run_input_LDADD = \
$(INPUT_LIBS) \
$(ARCHIVE_LIBS) \
+ libconf.a \
+ libutil.a \
+ libevent.a \
+ libfs.a \
$(GLIB_LIBS)
-test_run_input_SOURCES = test/run_input.c \
+test_run_input_SOURCES = test/run_input.cxx \
test/stdbin.h \
- src/io_thread.c src/io_thread.h \
- src/conf.c src/tokenizer.c src/utils.c src/string_util.c\
- src/tag.c src/tag_pool.c src/tag_save.c \
+ src/IOThread.cxx \
+ src/Tag.cxx src/TagNames.c src/TagPool.cxx src/TagSave.cxx \
src/fd_util.c
+if ENABLE_ARCHIVE
+
+test_visit_archive_LDADD = \
+ $(INPUT_LIBS) \
+ $(ARCHIVE_LIBS) \
+ libconf.a \
+ libutil.a \
+ libevent.a \
+ libfs.a \
+ $(GLIB_LIBS)
+test_visit_archive_SOURCES = test/visit_archive.cxx \
+ src/IOThread.cxx \
+ src/InputStream.cxx \
+ src/Tag.cxx src/TagNames.c src/TagPool.cxx \
+ src/fd_util.c
+
+if ENABLE_DESPOTIFY
+test_visit_archive_SOURCES += src/DespotifyUtils.cxx
+endif
+
+endif
+
test_dump_text_file_LDADD = \
$(INPUT_LIBS) \
$(ARCHIVE_LIBS) \
+ libconf.a \
+ libevent.a \
+ libfs.a \
+ libutil.a \
$(GLIB_LIBS)
-test_dump_text_file_SOURCES = test/dump_text_file.c \
+test_dump_text_file_SOURCES = test/dump_text_file.cxx \
test/stdbin.h \
- src/io_thread.c src/io_thread.h \
- src/conf.c src/tokenizer.c src/utils.c src/string_util.c\
- src/tag.c src/tag_pool.c \
- src/text_input_stream.c src/fifo_buffer.c \
+ src/IOThread.cxx \
+ src/Tag.cxx src/TagNames.c src/TagPool.cxx \
+ src/TextInputStream.cxx \
src/fd_util.c
test_dump_playlist_LDADD = \
@@ -1058,24 +1133,26 @@ test_dump_playlist_LDADD = \
$(ARCHIVE_LIBS) \
$(DECODER_LIBS) \
$(TAG_LIBS) \
+ libconf.a \
+ libevent.a \
+ libfs.a \
libutil.a \
+ libpcm.a \
$(GLIB_LIBS)
-test_dump_playlist_SOURCES = test/dump_playlist.c \
+test_dump_playlist_SOURCES = test/dump_playlist.cxx \
$(DECODER_SRC) \
- src/io_thread.c src/io_thread.h \
- src/conf.c src/tokenizer.c src/utils.c src/string_util.c\
- src/uri.c \
- src/song.c src/tag.c src/tag_pool.c src/tag_save.c \
- src/tag_handler.c src/tag_file.c \
- src/audio_check.c src/pcm_buffer.c \
- src/text_input_stream.c src/fifo_buffer.c \
- src/cue/cue_parser.c src/cue/cue_parser.h \
+ src/IOThread.cxx \
+ src/Song.cxx src/Tag.cxx src/TagNames.c src/TagPool.cxx src/TagSave.cxx \
+ src/TagHandler.cxx src/TagFile.cxx \
+ src/CheckAudioFormat.cxx \
+ src/TextInputStream.cxx \
+ src/cue/CueParser.cxx src/cue/CueParser.hxx \
src/fd_util.c
if HAVE_FLAC
test_dump_playlist_SOURCES += \
- src/replay_gain_info.c \
- src/decoder/flac_metadata.c
+ src/ReplayGainInfo.cxx \
+ src/decoder/FlacMetadata.cxx
endif
test_run_decoder_LDADD = \
@@ -1084,18 +1161,18 @@ test_run_decoder_LDADD = \
$(INPUT_LIBS) \
$(ARCHIVE_LIBS) \
$(TAG_LIBS) \
+ libconf.a \
+ libevent.a \
+ libfs.a \
libutil.a \
$(GLIB_LIBS)
-test_run_decoder_SOURCES = test/run_decoder.c \
+test_run_decoder_SOURCES = test/run_decoder.cxx \
test/stdbin.h \
- src/io_thread.c src/io_thread.h \
- src/conf.c src/tokenizer.c src/utils.c src/string_util.c src/log.c \
- src/tag.c src/tag_pool.c src/tag_handler.c \
- src/replay_gain_info.c \
- src/uri.c \
+ src/IOThread.cxx \
+ src/Tag.cxx src/TagNames.c src/TagPool.cxx src/TagHandler.cxx \
+ src/ReplayGainInfo.cxx \
src/fd_util.c \
- src/audio_check.c \
- src/audio_format.c \
+ src/AudioFormat.cxx src/CheckAudioFormat.cxx \
$(ARCHIVE_SRC) \
$(INPUT_SRC) \
$(TAG_SRC) \
@@ -1107,166 +1184,163 @@ test_read_tags_LDADD = \
$(INPUT_LIBS) \
$(ARCHIVE_LIBS) \
$(TAG_LIBS) \
+ libconf.a \
+ libevent.a \
+ libfs.a \
libutil.a \
$(GLIB_LIBS)
-test_read_tags_SOURCES = test/read_tags.c \
- src/io_thread.c src/io_thread.h \
- src/conf.c src/tokenizer.c src/utils.c src/string_util.c src/log.c \
- src/tag.c src/tag_pool.c src/tag_handler.c \
- src/replay_gain_info.c \
- src/uri.c \
+test_read_tags_SOURCES = test/read_tags.cxx \
+ src/IOThread.cxx \
+ src/Tag.cxx src/TagNames.c src/TagPool.cxx src/TagHandler.cxx \
+ src/ReplayGainInfo.cxx \
src/fd_util.c \
- src/audio_check.c \
+ src/CheckAudioFormat.cxx \
$(DECODER_SRC)
if HAVE_ID3TAG
test_dump_rva2_LDADD = \
$(ID3TAG_LIBS) \
$(GLIB_LIBS)
-test_dump_rva2_SOURCES = test/dump_rva2.c \
+test_dump_rva2_SOURCES = test/dump_rva2.cxx \
src/riff.c src/aiff.c \
- src/tag_handler.c \
- src/tag_id3.c \
- src/tag_rva2.c
+ src/TagHandler.cxx \
+ src/TagId3.cxx \
+ src/TagRva2.cxx
endif
test_run_filter_LDADD = \
$(FILTER_LIBS) \
+ libconf.a \
+ libutil.a \
+ libfs.a \
$(GLIB_LIBS)
-test_run_filter_SOURCES = test/run_filter.c \
+test_run_filter_SOURCES = test/run_filter.cxx \
+ test/FakeReplayGainConfig.cxx \
test/stdbin.h \
- src/filter_plugin.c \
- src/filter_registry.c \
- src/conf.c src/tokenizer.c src/utils.c src/string_util.c \
- src/audio_check.c \
- src/audio_format.c \
- src/audio_parser.c \
- src/replay_gain_config.c \
- src/replay_gain_info.c \
+ src/FilterPlugin.cxx src/FilterRegistry.cxx \
+ src/CheckAudioFormat.cxx \
+ src/AudioFormat.cxx \
+ src/AudioParser.cxx \
+ src/ReplayGainInfo.cxx \
src/AudioCompress/compress.c
if ENABLE_DESPOTIFY
-test_read_tags_SOURCES += \
- src/despotify_utils.c
-test_run_input_SOURCES += \
- src/despotify_utils.c
-test_dump_text_file_SOURCES += \
- src/despotify_utils.c
-test_dump_playlist_SOURCES += \
- src/despotify_utils.c
-test_run_decoder_SOURCES += \
- src/despotify_utils.c
+test_read_tags_SOURCES += src/DespotifyUtils.cxx
+test_run_input_SOURCES += src/DespotifyUtils.cxx
+test_dump_text_file_SOURCES += src/DespotifyUtils.cxx
+test_dump_playlist_SOURCES += src/DespotifyUtils.cxx
+test_run_decoder_SOURCES += src/DespotifyUtils.cxx
endif
if ENABLE_ENCODER
noinst_PROGRAMS += test/run_encoder
-test_run_encoder_SOURCES = test/run_encoder.c \
+test_run_encoder_SOURCES = test/run_encoder.cxx \
test/stdbin.h \
- src/fifo_buffer.c src/growing_fifo.c \
- src/conf.c src/tokenizer.c \
- src/utils.c src/string_util.c \
- src/tag.c src/tag_pool.c \
- src/audio_check.c \
- src/audio_format.c \
- src/audio_parser.c
+ src/Tag.cxx src/TagNames.c src/TagPool.cxx \
+ src/CheckAudioFormat.cxx \
+ src/AudioFormat.cxx \
+ src/AudioParser.cxx
test_run_encoder_LDADD = \
$(ENCODER_LIBS) \
- libpcm.a \
$(TAG_LIBS) \
+ libconf.a \
+ libpcm.a \
+ libfs.a \
+ libutil.a \
$(GLIB_LIBS)
endif
if ENABLE_VORBIS_ENCODER
noinst_PROGRAMS += test/test_vorbis_encoder
-test_test_vorbis_encoder_SOURCES = test/test_vorbis_encoder.c \
+test_test_vorbis_encoder_SOURCES = test/test_vorbis_encoder.cxx \
test/stdbin.h \
- src/conf.c src/tokenizer.c \
- src/utils.c \
- src/string_util.c \
- src/tag.c src/tag_pool.c \
- src/audio_check.c \
- src/audio_format.c \
- src/audio_parser.c \
- src/pcm_buffer.c \
- src/fifo_buffer.c src/growing_fifo.c \
+ src/Tag.cxx src/TagNames.c src/TagPool.cxx \
+ src/CheckAudioFormat.cxx \
+ src/AudioFormat.cxx \
+ src/AudioParser.cxx \
$(ENCODER_SRC)
test_test_vorbis_encoder_CPPFLAGS = $(AM_CPPFLAGS) \
$(ENCODER_CFLAGS)
test_test_vorbis_encoder_LDADD = $(MPD_LIBS) \
$(ENCODER_LIBS) \
+ $(PCM_LIBS) \
+ libconf.a \
+ libfs.a \
+ libutil.a \
$(GLIB_LIBS)
endif
-test_software_volume_SOURCES = test/software_volume.c \
+test_software_volume_SOURCES = test/software_volume.cxx \
test/stdbin.h \
- src/audio_check.c \
- src/audio_parser.c
+ src/CheckAudioFormat.cxx \
+ src/AudioParser.cxx
test_software_volume_LDADD = \
$(PCM_LIBS) \
$(GLIB_LIBS)
-test_run_normalize_SOURCES = test/run_normalize.c \
+test_run_normalize_SOURCES = test/run_normalize.cxx \
test/stdbin.h \
- src/audio_check.c \
- src/audio_parser.c \
+ src/CheckAudioFormat.cxx \
+ src/AudioParser.cxx \
src/AudioCompress/compress.c
test_run_normalize_LDADD = \
$(GLIB_LIBS)
-test_run_convert_SOURCES = test/run_convert.c \
- src/dsd2pcm/dsd2pcm.c \
- src/fifo_buffer.c \
- src/audio_format.c \
- src/audio_check.c \
- src/audio_parser.c
+test_run_convert_SOURCES = test/run_convert.cxx \
+ src/AudioFormat.cxx \
+ src/CheckAudioFormat.cxx \
+ src/AudioParser.cxx
test_run_convert_LDADD = \
$(PCM_LIBS) \
libutil.a \
$(GLIB_LIBS)
test_run_output_LDADD = $(MPD_LIBS) \
+ $(PCM_LIBS) \
$(OUTPUT_LIBS) \
$(ENCODER_LIBS) \
libmixer_plugins.a \
$(FILTER_LIBS) \
+ libconf.a \
+ libevent.a \
+ libfs.a \
libutil.a \
$(GLIB_LIBS)
-test_run_output_SOURCES = test/run_output.c \
+test_run_output_SOURCES = test/run_output.cxx \
+ test/FakeReplayGainConfig.cxx \
test/stdbin.h \
- src/conf.c src/tokenizer.c src/utils.c src/string_util.c src/log.c \
- src/io_thread.c src/io_thread.h \
- src/audio_check.c \
- src/audio_format.c \
- src/audio_parser.c \
- src/timer.c src/clock.c \
- src/tag.c src/tag_pool.c \
- src/fifo_buffer.c src/growing_fifo.c \
- src/page.c \
- src/socket_util.c \
+ src/IOThread.cxx \
+ src/CheckAudioFormat.cxx \
+ src/AudioFormat.cxx \
+ src/AudioParser.cxx \
+ src/Timer.cxx src/clock.c \
+ src/Tag.cxx src/TagNames.c src/TagPool.cxx \
+ src/Page.cxx \
+ src/SocketUtil.cxx \
src/resolver.c \
- src/output_init.c src/output_finish.c src/output_list.c \
- src/output_plugin.c \
- src/mixer_api.c \
- src/mixer_control.c \
- src/mixer_type.c \
- src/filter_plugin.c \
- src/filter_config.c \
+ src/OutputInit.cxx src/OutputFinish.cxx src/OutputList.cxx \
+ src/OutputPlugin.cxx \
+ src/MixerControl.cxx \
+ src/MixerType.cxx \
+ src/FilterPlugin.cxx \
+ src/FilterConfig.cxx \
src/AudioCompress/compress.c \
- src/replay_gain_info.c \
- src/replay_gain_config.c \
- src/fd_util.c \
- src/server_socket.c
+ src/ReplayGainInfo.cxx \
+ src/fd_util.c
test_read_mixer_LDADD = \
libpcm.a \
libmixer_plugins.a \
$(OUTPUT_LIBS) \
+ libconf.a \
+ libutil.a \
+ libevent.a \
+ libfs.a \
$(GLIB_LIBS)
-test_read_mixer_SOURCES = test/read_mixer.c \
- src/conf.c src/tokenizer.c src/utils.c src/string_util.c src/log.c \
- src/mixer_control.c src/mixer_api.c \
- src/filter_plugin.c \
- src/filter/volume_filter_plugin.c \
+test_read_mixer_SOURCES = test/read_mixer.cxx \
+ src/MixerControl.cxx \
+ src/FilterPlugin.cxx \
+ src/filter/VolumeFilterPlugin.cxx \
src/fd_util.c
if ENABLE_BZIP2_TEST
@@ -1283,11 +1357,13 @@ endif
if ENABLE_INOTIFY
noinst_PROGRAMS += test/run_inotify
-test_run_inotify_SOURCES = test/run_inotify.c \
+test_run_inotify_SOURCES = test/run_inotify.cxx \
src/fd_util.c \
- src/fifo_buffer.c \
- src/inotify_source.c
-test_run_inotify_LDADD = $(GLIB_LIBS)
+ src/InotifySource.cxx
+test_run_inotify_LDADD = \
+ libevent.a \
+ libutil.a \
+ $(GLIB_LIBS)
endif
test_test_byte_reverse_SOURCES = \
@@ -1297,32 +1373,35 @@ test_test_byte_reverse_LDADD = \
$(GLIB_LIBS)
test_test_pcm_SOURCES = \
- test/test_pcm_dither.c \
- test/test_pcm_pack.c \
- test/test_pcm_channels.c \
- test/test_pcm_volume.c \
- test/test_pcm_all.h \
- test/test_pcm_main.c
+ test/test_pcm_util.hxx \
+ test/test_pcm_dither.cxx \
+ test/test_pcm_pack.cxx \
+ test/test_pcm_channels.cxx \
+ test/test_pcm_format.cxx \
+ test/test_pcm_volume.cxx \
+ test/test_pcm_mix.cxx \
+ test/test_pcm_all.hxx \
+ test/test_pcm_main.cxx
test_test_pcm_LDADD = \
$(PCM_LIBS) \
libutil.a \
$(GLIB_LIBS)
test_test_queue_priority_SOURCES = \
- src/queue.c \
- test/test_queue_priority.c
+ src/Queue.cxx \
+ src/fd_util.c \
+ test/test_queue_priority.cxx
test_test_queue_priority_LDADD = \
+ libutil.a \
$(GLIB_LIBS)
-if HAVE_CXX
-noinst_PROGRAMS += src/dsd2pcm/dsd2pcm
+noinst_PROGRAMS += src/pcm/dsd2pcm/dsd2pcm
-src_dsd2pcm_dsd2pcm_SOURCES = \
- src/dsd2pcm/dsd2pcm.c src/dsd2pcm/dsd2pcm.h \
- src/dsd2pcm/noiseshape.c src/dsd2pcm/noiseshape.h \
- src/dsd2pcm/main.cpp
-src_dsd2pcm_dsd2pcm_LDADD = libutil.a
-endif
+src_pcm_dsd2pcm_dsd2pcm_SOURCES = \
+ src/pcm/dsd2pcm/dsd2pcm.c src/pcm/dsd2pcm/dsd2pcm.h \
+ src/pcm/dsd2pcm/noiseshape.c src/pcm/dsd2pcm/noiseshape.h \
+ src/pcm/dsd2pcm/main.cpp
+src_pcm_dsd2pcm_dsd2pcm_LDADD = libutil.a
endif
diff --git a/NEWS b/NEWS
index f7c45514b..46ee9dbb1 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,25 @@
+ver 0.18 (2012/??/??)
+* innput:
+ - soup: plugin removed
+* decoder:
+ - adplug: new decoder plugin using libadplug
+ - ffmpeg: drop support for pre-0.8 ffmpeg
+ - flac: require libFLAC 1.2 or newer
+ - flac: support FLAC files inside archives
+ - opus: new decoder plugin for the Opus codec
+ - vorbis: skip 16 bit quantisation, provide float samples
+ - mp4ff: obsolete plugin removed
+* encoder:
+ - opus: new encoder plugin for the Opus codec
+ - vorbis: accept floating point input samples
+* output:
+ - new option "tags" may be used to disable sending tags to output
+ - alsa: workaround for noise after manual song change
+ - ffado: remove broken plugin
+ - mvp: remove obsolete plugin
+* improved decoder/output error reporting
+* eliminate timer wakeup on idle MPD
+
ver 0.17.5 (2013/08/04)
* protocol:
- fix "playlistadd" with URI
diff --git a/configure.ac b/configure.ac
index fa2ec19ac..2816a2b48 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,13 +1,13 @@
AC_PREREQ(2.60)
-AC_INIT(mpd, 0.17.5, musicpd-dev-team@lists.sourceforge.net)
+AC_INIT(mpd, 0.18~git, musicpd-dev-team@lists.sourceforge.net)
VERSION_MAJOR=0
-VERSION_MINOR=17
+VERSION_MINOR=18
VERSION_REVISION=0
VERSION_EXTRA=0
-AC_CONFIG_SRCDIR([src/main.c])
+AC_CONFIG_SRCDIR([src/Main.cxx])
AM_INIT_AUTOMAKE([foreign 1.11 dist-bzip2 dist-xz subdir-objects])
AM_SILENT_RULES
AC_CONFIG_HEADERS(config.h)
@@ -28,22 +28,6 @@ AN_PROGRAM([ar], [AC_PROG_AR])
AC_DEFUN([AC_PROG_AR], [AC_CHECK_TOOL(AR, ar, :)])
AC_PROG_AR
-HAVE_CXX=yes
-if test x$CXX = xg++; then
- # CXX=g++ probably means that autoconf hasn't found any C++
- # compiler; to be sure, we check again
- AC_PATH_PROG(CXX, $CXX, no)
- if test x$CXX = xno; then
- # no, we don't have C++ - the following hack is
- # required because automake insists on using $(CXX)
- # for linking the MPD binary
- AC_MSG_NOTICE([Disabling C++ support])
- CXX="$CC"
- HAVE_CXX=no
- fi
-fi
-AM_CONDITIONAL(HAVE_CXX, test x$HAVE_CXX = xyes)
-
AC_PROG_INSTALL
AC_PROG_MAKE_SET
PKG_PROG_PKG_CONFIG
@@ -87,7 +71,8 @@ mingw32* | windows*)
src/win/mpd_win32_rc.rc
])
AC_CHECK_TOOL(WINDRES, windres)
- AM_CPPFLAGS="$AM_CPPFLAGS -DWINVER=0x0501"
+ 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
;;
@@ -131,6 +116,19 @@ if test -z "$prefix" || test "x$prefix" = xNONE; then
fi
dnl ---------------------------------------------------------------------------
+dnl Language Checks
+dnl ---------------------------------------------------------------------------
+
+AC_CXX_COMPILE_STDCXX_0X
+if test "$ax_cv_cxx_compile_cxx0x_native" != yes; then
+ if test "$ax_cv_cxx_compile_cxx0x_gxx" = yes; then
+ AM_CXXFLAGS="$AM_CXXFLAGS -std=gnu++0x"
+ elif test "$ax_cv_cxx_compile_cxx0x_cxx" = yes; then
+ AM_CXXFLAGS="$AM_CXXFLAGS -std=c++0x"
+ fi
+fi
+
+dnl ---------------------------------------------------------------------------
dnl Header/Library Checks
dnl ---------------------------------------------------------------------------
AC_CHECK_FUNCS(daemon fork)
@@ -141,7 +139,7 @@ AC_SEARCH_LIBS([syslog], [bsd socket inet],
AC_SEARCH_LIBS([socket], [socket])
AC_SEARCH_LIBS([gethostbyname], [nsl])
-AC_CHECK_FUNCS(pipe2 accept4)
+AC_CHECK_FUNCS(pipe2 accept4 eventfd)
AC_SEARCH_LIBS([exp], [m],,
[AC_MSG_ERROR([exp() not found])])
@@ -152,6 +150,17 @@ AC_CHECK_HEADERS(valgrind/memcheck.h)
dnl ---------------------------------------------------------------------------
dnl Allow tools to be specifically built
dnl ---------------------------------------------------------------------------
+
+AC_ARG_ENABLE(libmpdclient,
+ AS_HELP_STRING([--enable-libmpdclient],
+ [enable support for the MPD client]),,
+ enable_libmpdclient=auto)
+
+AC_ARG_ENABLE(adplug,
+ AS_HELP_STRING([--enable-adplug],
+ [enable the AdPlug decoder plugin (default: auto)]),,
+ enable_adplug=auto)
+
AC_ARG_ENABLE(alsa,
AS_HELP_STRING([--enable-alsa], [enable ALSA support]),,
[enable_alsa=auto])
@@ -186,11 +195,6 @@ AC_ARG_ENABLE(curl,
[enable support for libcurl HTTP streaming (default: auto)]),,
[enable_curl=auto])
-AC_ARG_ENABLE(soup,
- AS_HELP_STRING([--enable-soup],
- [enable support for libsoup HTTP streaming (default: auto)]),,
- [enable_soup=auto])
-
AC_ARG_ENABLE(debug,
AS_HELP_STRING([--enable-debug],
[enable debugging (default: disabled)]),,
@@ -201,10 +205,6 @@ AC_ARG_ENABLE(documentation,
[build documentation (default: disable)]),,
[enable_documentation=no])
-AC_ARG_ENABLE(ffado,
- AS_HELP_STRING([--enable-ffado], [enable libffado (FireWire) support]),,
- [enable_ffado=no])
-
AC_ARG_ENABLE(ffmpeg,
AS_HELP_STRING([--enable-ffmpeg],
[enable FFMPEG support]),,
@@ -321,16 +321,16 @@ AC_ARG_ENABLE(mpg123,
[enable libmpg123 decoder plugin]),,
enable_mpg123=auto)
-AC_ARG_ENABLE(mvp,
- AS_HELP_STRING([--enable-mvp],
- [enable support for Hauppauge Media MVP (default: disable)]),,
- enable_mvp=no)
-
AC_ARG_ENABLE(openal,
AS_HELP_STRING([--enable-openal],
[enable OpenAL support (default: disable)]),,
enable_openal=no)
+AC_ARG_ENABLE(opus,
+ AS_HELP_STRING([--enable-opus],
+ [enable Opus codec support (default: auto)]),,
+ enable_opus=auto)
+
AC_ARG_ENABLE(oss,
AS_HELP_STRING([--disable-oss],
[disable OSS support (default: enable)]),,
@@ -461,8 +461,8 @@ AC_ARG_WITH(tremor-includes,
dnl ---------------------------------------------------------------------------
dnl Mandatory Libraries
dnl ---------------------------------------------------------------------------
-PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.16 gthread-2.0],,
- [AC_MSG_ERROR([GLib 2.16 is required])])
+PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.24 gthread-2.0],,
+ [AC_MSG_ERROR([GLib 2.24 is required])])
if test x$GCC = xyes; then
# suppress warnings in the GLib headers
@@ -542,6 +542,15 @@ dnl ---------------------------------------------------------------------------
dnl Miscellaneous Libraries
dnl ---------------------------------------------------------------------------
+dnl -------------------------------- libmpdclient --------------------------------
+MPD_AUTO_PKG(libmpdclient, LIBMPDCLIENT, [libmpdclient >= 2.2],
+ [MPD client library], [libmpdclient not found])
+if test x$enable_libmpdclient = xyes; then
+ AC_DEFINE(HAVE_LIBMPDCLIENT, 1, [Define to use libmpdclient])
+fi
+
+AM_CONDITIONAL(HAVE_LIBMPDCLIENT, test x$enable_libmpdclient = xyes)
+
dnl --------------------------------- inotify ---------------------------------
AC_CHECK_FUNCS(inotify_init inotify_init1)
@@ -557,6 +566,27 @@ AM_CONDITIONAL(ENABLE_INOTIFY, test x$enable_inotify = xyes)
dnl --------------------------------- libwrap ---------------------------------
if test x$enable_libwrap != xno; then
AC_CHECK_LIBWRAP(found_libwrap=yes, found_libwrap=no)
+
+ if test x$found_libwrap = xyes; then
+ dnl See if libwrap is compatible with C++; it is
+ dnl broken on many systems
+ AC_MSG_CHECKING(whether libwrap is compatible with C++)
+ AC_LANG_PUSH([C++])
+ AC_COMPILE_IFELSE([AC_LANG_SOURCE([
+ #include <tcpd.h>
+ bool CheckLibWrap(int fd, const char &progname) {
+ struct request_info req;
+ request_init(&req, RQ_FILE, fd, RQ_DAEMON, progname, 0);
+ fromhost(&req);
+ return hosts_access(&req);
+ }
+ ])],
+ AC_MSG_RESULT([yes]),
+ [found_libwrap=no; AC_MSG_RESULT([no]);
+ AC_MSG_WARN([Your version of libwrap is broken with C++])])
+ AC_LANG_POP
+ fi
+
MPD_AUTO_RESULT(libwrap, libwrap, [libwrap not found])
fi
@@ -674,21 +704,13 @@ dnl Input Plugins
dnl ---------------------------------------------------------------------------
dnl ----------------------------------- CURL ----------------------------------
-MPD_AUTO_PKG(curl, CURL, [libcurl],
+MPD_AUTO_PKG(curl, CURL, [libcurl >= 7.18],
[libcurl HTTP streaming], [libcurl not found])
if test x$enable_curl = xyes; then
AC_DEFINE(ENABLE_CURL, 1, [Define when libcurl is used for HTTP streaming])
fi
AM_CONDITIONAL(ENABLE_CURL, test x$enable_curl = xyes)
-dnl ----------------------------------- SOUP ----------------------------------
-MPD_AUTO_PKG(soup, SOUP, [libsoup-2.4],
- [libsoup HTTP streaming], [libsoup not found])
-if test x$enable_soup = xyes; then
- AC_DEFINE(ENABLE_SOUP, 1, [Define when libsoup is used for HTTP streaming])
-fi
-AM_CONDITIONAL(ENABLE_SOUP, test x$enable_soup = xyes)
-
dnl --------------------------------- Last.FM ---------------------------------
if test x$enable_lastfm = xyes; then
if test x$enable_curl != xyes; then
@@ -811,6 +833,14 @@ dnl ---------------------------------------------------------------------------
dnl Decoder Plugins
dnl ---------------------------------------------------------------------------
+dnl -------------------------------- libadplug --------------------------------
+MPD_AUTO_PKG(adplug, ADPLUG, [adplug],
+ [AdPlug decoder plugin], [libadplug not found])
+if test x$enable_adplug = xyes; then
+ AC_DEFINE(HAVE_ADPLUG, 1, [Define to use libadplug])
+fi
+AM_CONDITIONAL(HAVE_ADPLUG, test x$enable_adplug = xyes)
+
dnl -------------------------------- audiofile --------------------------------
MPD_AUTO_PKG(audiofile, AUDIOFILE, [audiofile >= 0.1.7],
[audiofile decoder plugin], [libaudiofile not found])
@@ -823,10 +853,9 @@ dnl ----------------------------------- FAAD ----------------------------------
AM_PATH_FAAD()
AM_CONDITIONAL(HAVE_FAAD, test x$enable_aac = xyes)
-AM_CONDITIONAL(HAVE_MP4, test x$enable_mp4 = xyes)
dnl ---------------------------------- ffmpeg ---------------------------------
-MPD_AUTO_PKG(ffmpeg, FFMPEG, [libavformat >= 52.31 libavcodec >= 52.20 libavutil >= 49.15],
+MPD_AUTO_PKG(ffmpeg, FFMPEG, [libavformat >= 53.17 libavcodec >= 53.25 libavutil >= 51.17],
[ffmpeg decoder library], [libavformat+libavcodec+libavutil not found])
if test x$enable_ffmpeg = xyes; then
@@ -837,7 +866,7 @@ AM_CONDITIONAL(HAVE_FFMPEG, test x$enable_ffmpeg = xyes)
dnl ----------------------------------- FLAC ----------------------------------
-MPD_AUTO_PKG(flac, FLAC, [flac >= 1.1],
+MPD_AUTO_PKG(flac, FLAC, [flac >= 1.2],
[FLAC decoder], [libFLAC not found])
if test x$enable_flac = xyes; then
@@ -904,9 +933,6 @@ fi
AM_CONDITIONAL(ENABLE_MIKMOD_DECODER, test x$enable_mikmod = xyes)
dnl -------------------------------- libmodplug -------------------------------
-found_modplug=$HAVE_CXX
-MPD_AUTO_PRE(modplug, [modplug decoder plugin], [No C++ compiler found])
-
MPD_AUTO_PKG(modplug, MODPLUG, [libmodplug],
[modplug decoder plugin], [libmodplug not found])
@@ -915,6 +941,14 @@ if test x$enable_modplug = xyes; then
fi
AM_CONDITIONAL(HAVE_MODPLUG, test x$enable_modplug = xyes)
+dnl -------------------------------- libopus ----------------------------------
+MPD_AUTO_PKG(opus, OPUS, [opus ogg],
+ [opus decoder plugin], [libopus not found])
+if test x$enable_opus = xyes; then
+ AC_DEFINE(HAVE_OPUS, 1, [Define to use libopus])
+fi
+AM_CONDITIONAL(HAVE_OPUS, test x$enable_opus = xyes)
+
dnl -------------------------------- libsndfile -------------------------------
dnl See above test, which may disable this.
MPD_AUTO_PKG(sndfile, SNDFILE, [sndfile],
@@ -937,16 +971,7 @@ if test x$enable_mpc = xyes; then
LIBS=$oldlibs
CPPFLAGS=$oldcppflags
- if test x$enable_mpc = xyes; then
- AC_CHECK_HEADER([mpc/mpcdec.h],
- [AC_DEFINE(HAVE_MPCDEC,1,
- [Define to use libmpcdec for MPC decoding])],
- [AC_CHECK_HEADER(mpcdec/mpcdec.h,
- [AC_DEFINE(MPC_IS_OLD_API, 1,
- [Define if an old pre-SV8 libmpcdec is used])]
- )]
- )
- else
+ if test x$enable_mpc != xyes; then
AC_MSG_WARN([mpcdec lib needed for MPC support -- disabling MPC support])
fi
fi
@@ -1020,9 +1045,6 @@ fi
AM_CONDITIONAL(ENABLE_VORBIS_DECODER, test x$enable_vorbis = xyes || test x$enable_tremor = xyes)
dnl --------------------------------- sidplay ---------------------------------
-found_sidplay=$HAVE_CXX
-MPD_AUTO_PRE(sidplay, [sidplay decoder plugin], [No C++ compiler found])
-
if test x$enable_sidplay != xno; then
# we're not using pkg-config here
# because libsidplay2's .pc file requires libtool
@@ -1097,9 +1119,9 @@ if
test x$enable_mad = xno &&
test x$enable_mikmod = xno; then
test x$enable_modplug = xno &&
- test x$enable_mp4 = xno &&
test x$enable_mpc = xno &&
test x$enable_mpg123 = xno &&
+ test x$enable_opus = xno &&
test x$enable_sidplay = xno &&
test x$enable_tremor = xno &&
test x$enable_vorbis = xno &&
@@ -1109,11 +1131,8 @@ if
AC_MSG_ERROR([No input plugins supported!])
fi
-AM_CONDITIONAL(HAVE_OGG_COMMON,
- test x$enable_vorbis = xyes || test x$enable_tremor = xyes || test x$enable_flac = xyes)
-
-AM_CONDITIONAL(HAVE_FLAC_COMMON,
- test x$enable_flac = xyes)
+AM_CONDITIONAL(HAVE_XIPH,
+ test x$enable_vorbis = xyes || test x$enable_tremor = xyes || test x$enable_flac = xyes || test x$enable_opus = xyes)
dnl ---------------------------------------------------------------------------
dnl Encoders for Streaming Audio Output Plugins
@@ -1201,6 +1220,7 @@ fi
dnl --------------------------- encoder plugins test --------------------------
if test x$enable_vorbis_encoder != xno ||
+ test x$enable_opus != xno ||
test x$enable_lame_encoder != xno ||
test x$enable_twolame_encoder != xno ||
test x$enable_flac_encoder != xno ||
@@ -1246,17 +1266,6 @@ fi
AM_CONDITIONAL(HAVE_ROAR, test x$enable_roar = xyes)
-dnl ----------------------------------- FFADO ---------------------------------
-
-MPD_AUTO_PKG(ffado, FFADO, [libffado],
- [libffado output plugin], [libffado not found])
-
-if test x$enable_ffado = xyes; then
- AC_DEFINE(ENABLE_FFADO_OUTPUT, 1, [Define to enable the libffado output plugin])
-fi
-
-AM_CONDITIONAL(ENABLE_FFADO_OUTPUT, test x$enable_ffado = xyes)
-
dnl ----------------------------------- FIFO ----------------------------------
if test x$enable_fifo = xyes; then
AC_CHECK_FUNC([mkfifo],
@@ -1312,13 +1321,6 @@ fi
AM_CONDITIONAL(HAVE_AO, test x$enable_ao = xyes)
-dnl ----------------------------------- MVP -----------------------------------
-if test x$enable_mvp = xyes; then
- AC_DEFINE(HAVE_MVP,1,[Define to enable Hauppauge Media MVP support])
-fi
-
-AM_CONDITIONAL(HAVE_MVP, test x$enable_mvp = xyes)
-
dnl ---------------------------------- OpenAL ---------------------------------
AC_SUBST(OPENAL_CFLAGS,"")
AC_SUBST(OPENAL_LIBS,"")
@@ -1454,11 +1456,9 @@ if
test x$enable_alsa = xno &&
test x$enable_roar = xno &&
test x$enable_ao = xno &&
- test x$enable_ffado = xno &&
test x$enable_fifo = xno &&
test x$enable_httpd_output = xno &&
test x$enable_jack = xno &&
- test x$enable_mvp = xno &&
test x$enable_openal = xno &&
test x$enable_oss = xno &&
test x$enable_osx = xno &&
@@ -1504,6 +1504,22 @@ dnl ---------------------------------------------------------------------------
dnl ---------------------------------- debug ----------------------------------
if test "x$enable_debug" = xno; then
AM_CPPFLAGS="$AM_CPPFLAGS -DNDEBUG"
+
+ AX_APPEND_COMPILE_FLAGS([-ffunction-sections])
+ AX_APPEND_COMPILE_FLAGS([-fdata-sections])
+ AX_APPEND_COMPILE_FLAGS([-fvisibility=hidden])
+
+ AC_LANG_PUSH([C++])
+ AX_APPEND_COMPILE_FLAGS([-ffunction-sections])
+ AX_APPEND_COMPILE_FLAGS([-fdata-sections])
+ AX_APPEND_COMPILE_FLAGS([-fvisibility=hidden])
+ AX_APPEND_COMPILE_FLAGS([-fno-threadsafe-statics])
+ AX_APPEND_COMPILE_FLAGS([-fmerge-all-constants])
+ AX_APPEND_COMPILE_FLAGS([-fno-exceptions])
+ AX_APPEND_COMPILE_FLAGS([-fno-rtti])
+ AC_LANG_POP
+
+ AX_APPEND_LINK_FLAGS([-Wl,--gc-sections])
fi
dnl ----------------------------------- GCC -----------------------------------
@@ -1518,6 +1534,17 @@ then
AX_APPEND_COMPILE_FLAGS([-Wcast-qual])
AX_APPEND_COMPILE_FLAGS([-Wwrite-strings])
AX_APPEND_COMPILE_FLAGS([-pedantic])
+
+ AC_LANG_PUSH([C++])
+ AX_APPEND_COMPILE_FLAGS([-Wall])
+ AX_APPEND_COMPILE_FLAGS([-Wextra])
+ AX_APPEND_COMPILE_FLAGS([-Wmissing-declarations])
+ AX_APPEND_COMPILE_FLAGS([-Wshadow])
+ AX_APPEND_COMPILE_FLAGS([-Wpointer-arith])
+ AX_APPEND_COMPILE_FLAGS([-Wcast-qual])
+ AX_APPEND_COMPILE_FLAGS([-Wwrite-strings])
+ AX_APPEND_COMPILE_FLAGS([-Wsign-compare])
+ AC_LANG_POP
fi
dnl ---------------------------- warnings as errors ---------------------------
@@ -1550,20 +1577,21 @@ results(un,[UNIX Domain Sockets])
printf '\nFile format support:\n\t'
results(aac, [AAC])
+results(adplug, [AdPlug])
results(sidplay, [C64 SID])
results(ffmpeg, [FFMPEG])
results(flac, [FLAC])
results(fluidsynth, [FluidSynth])
results(gme, [GME])
-results(sndfile, [libsndfile])
printf '\n\t'
+results(sndfile, [libsndfile])
results(mikmod, [MikMod])
results(modplug, [MODPLUG])
results(mad, [MAD])
results(mpg123, [MPG123])
-results(mp4, [MP4])
results(mpc, [Musepack])
printf '\n\t'
+results(opus, [Opus])
results(tremor, [OggTremor])
results(vorbis, [OggVorbis])
results(audiofile, [WAVE])
@@ -1572,6 +1600,7 @@ results(wildmidi, [WildMidi])
printf '\nOther features:\n\t'
results(lsr, [libsamplerate])
+results(libmpdclient, [libmpdclient])
results(inotify, [inotify])
results(sqlite, [SQLite])
@@ -1580,14 +1609,12 @@ results(id3,[ID3])
printf '\nPlayback support:\n\t'
results(alsa,ALSA)
-results(ffado,FFADO)
results(fifo,FIFO)
results(recorder_output,[File Recorder])
results(httpd_output,[HTTP Daemon])
results(jack,[JACK])
printf '\n\t'
results(ao,[libao])
-results(mvp, [Media MVP])
results(oss,[OSS])
results(openal,[OpenAL])
results(osx, [OS X])
@@ -1607,6 +1634,7 @@ if
results(flac_encoder, [FLAC])
results(lame_encoder, [LAME])
results(vorbis_encoder, [Ogg Vorbis])
+ results(opus, [Opus])
results(twolame_encoder, [TwoLAME])
results(wave_encoder, [WAVE])
fi
@@ -1619,7 +1647,6 @@ results(lastfm,[Last.FM])
results(soundcloud,[Soundcloud])
printf '\n\t'
results(mms,[MMS])
-results(soup, [SOUP])
printf '\n\n##########################################\n\n'
diff --git a/doc/user.xml b/doc/user.xml
index 38d8a9d85..0f84800c1 100644
--- a/doc/user.xml
+++ b/doc/user.xml
@@ -165,6 +165,53 @@ systemctl start mpd.socket</programlisting>
</section>
<section>
+ <title>Configuring database plugins</title>
+
+ <para>
+ If a music directory is configured, one database plugin is
+ used. To configure this plugin, add a
+ <varname>database</varname> block to
+ <filename>mpd.conf</filename>:
+ </para>
+
+ <programlisting>database {
+ plugin "simple"
+ path "/var/lib/mpd/db"
+}
+ </programlisting>
+
+ <para>
+ The following table lists the <varname>database</varname>
+ options valid for all plugins:
+ </para>
+
+ <informaltable>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>
+ Name
+ </entry>
+ <entry>
+ Description
+ </entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>
+ <varname>plugin</varname>
+ </entry>
+ <entry>
+ The name of the plugin.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </informaltable>
+ </section>
+
+ <section>
<title>Configuring input plugins</title>
<para>
@@ -391,6 +438,18 @@ systemctl start mpd.socket</programlisting>
</row>
<row>
<entry>
+ <varname>tags</varname>
+ <parameter>yes|no</parameter>
+ </entry>
+ <entry>
+ If set to "no", then MPD will not send tags to this
+ output. This is only useful for output plugins that
+ can receive tags, for example the
+ <varname>httpd</varname> output plugin.
+ </entry>
+ </row>
+ <row>
+ <entry>
<varname>always_on</varname>
<parameter>yes|no</parameter>
</entry>
@@ -618,6 +677,78 @@ systemctl start mpd.socket</programlisting>
<title>Plugin reference</title>
<section>
+ <title>Database plugins</title>
+
+ <section>
+ <title><varname>simple</varname></title>
+
+ <para>
+ The default plugin. Stores a copy of the database in
+ memory. A file is used for permanent storage.
+ </para>
+
+ <informaltable>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Setting</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>
+ <varname>path</varname>
+ </entry>
+ <entry>
+ The path of the database file.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </informaltable>
+ </section>
+
+ <section>
+ <title><varname>proxy</varname></title>
+
+ <para>
+ Provides access to the database of another MPD instance
+ using <filename>libmpdclient</filename>. Experimental!
+ </para>
+
+ <informaltable>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Setting</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>
+ <varname>host</varname>
+ </entry>
+ <entry>
+ The host name of the "master" MPD instance.
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <varname>port</varname>
+ </entry>
+ <entry>
+ The port number of the "master" MPD instance.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </informaltable>
+ </section>
+ </section>
+
+ <section>
<title>Input plugins</title>
<section>
@@ -744,35 +875,6 @@ systemctl start mpd.socket</programlisting>
</tgroup>
</informaltable>
</section>
-
- <section>
- <title><varname>soup</varname></title>
-
- <para>
- Opens remote files or streams over HTTP.
- </para>
-
- <informaltable>
- <tgroup cols="2">
- <thead>
- <row>
- <entry>Setting</entry>
- <entry>Description</entry>
- </row>
- </thead>
- <tbody>
- <row>
- <entry>
- <varname>proxy</varname>
- </entry>
- <entry>
- Sets the address of the HTTP proxy server.
- </entry>
- </row>
- </tbody>
- </tgroup>
- </informaltable>
- </section>
</section>
<section>
@@ -1241,43 +1343,6 @@ systemctl start mpd.socket</programlisting>
</section>
<section>
- <title><varname>ffado</varname></title>
-
- <para>
- The <varname>ffado</varname> plugin connects to FireWire
- audio devices via <filename>libffado</filename>.
- </para>
-
- <para>
- Warning: this plugin was not tested successfully. I just
- couldn't keep libffado2 from crashing. Use at your own
- risk.
- </para>
-
- <informaltable>
- <tgroup cols="2">
- <thead>
- <row>
- <entry>Setting</entry>
- <entry>Description</entry>
- </row>
- </thead>
- <tbody>
- <row>
- <entry>
- <varname>device</varname>
- <parameter>NAME</parameter>
- </entry>
- <entry>
- Sets the device which should be used, e.g. "hw:0".
- </entry>
- </row>
- </tbody>
- </tgroup>
- </informaltable>
- </section>
-
- <section>
<title><varname>jack</varname></title>
<para>
@@ -1362,16 +1427,6 @@ systemctl start mpd.socket</programlisting>
</section>
<section>
- <title><varname>mvp</varname></title>
-
- <para>
- The <varname>mvp</varname> plugin uses the proprietary
- Hauppauge Media MVP interface. We do not know any user of
- this plugin, and we do not know if it actually works.
- </para>
- </section>
-
- <section>
<title><varname>httpd</varname></title>
<para>
diff --git a/m4/ax_append_link_flags.m4 b/m4/ax_append_link_flags.m4
new file mode 100644
index 000000000..4fc433700
--- /dev/null
+++ b/m4/ax_append_link_flags.m4
@@ -0,0 +1,61 @@
+# ===========================================================================
+# http://www.gnu.org/software/autoconf-archive/ax_append_link_flags.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_APPEND_LINK_FLAGS([FLAG1 FLAG2 ...], [FLAGS-VARIABLE], [EXTRA-FLAGS])
+#
+# DESCRIPTION
+#
+# For every FLAG1, FLAG2 it is checked whether the linker works with the
+# flag. If it does, the flag is added FLAGS-VARIABLE
+#
+# If FLAGS-VARIABLE is not specified, the linker's flags (LDFLAGS) is
+# used. During the check the flag is always added to the linker's flags.
+#
+# If EXTRA-FLAGS is defined, it is added to the linker's default flags
+# when the check is done. The check is thus made with the flags: "LDFLAGS
+# EXTRA-FLAGS FLAG". This can for example be used to force the linker to
+# issue an error when a bad flag is given.
+#
+# NOTE: This macro depends on the AX_APPEND_FLAG and AX_CHECK_LINK_FLAG.
+# Please keep this macro in sync with AX_APPEND_COMPILE_FLAGS.
+#
+# LICENSE
+#
+# Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+#
+# As a special exception, the respective Autoconf Macro's copyright owner
+# gives unlimited permission to copy, distribute and modify the configure
+# scripts that are the output of Autoconf when processing the Macro. You
+# need not follow the terms of the GNU General Public License when using
+# or distributing such scripts, even though portions of the text of the
+# Macro appear in them. The GNU General Public License (GPL) does govern
+# all other use of the material that constitutes the Autoconf Macro.
+#
+# This special exception to the GPL applies to versions of the Autoconf
+# Macro released by the Autoconf Archive. When you make and distribute a
+# modified version of the Autoconf Macro, you may extend this special
+# exception to the GPL to apply to your modified version as well.
+
+#serial 2
+
+AC_DEFUN([AX_APPEND_LINK_FLAGS],
+[for flag in $1; do
+ AX_CHECK_LINK_FLAG([$flag], [AX_APPEND_FLAG([$flag], [m4_default([$2], [LDFLAGS])])], [], [$3])
+done
+])dnl AX_APPEND_LINK_FLAGS
diff --git a/m4/ax_check_link_flag.m4 b/m4/ax_check_link_flag.m4
new file mode 100644
index 000000000..e2d0d363e
--- /dev/null
+++ b/m4/ax_check_link_flag.m4
@@ -0,0 +1,71 @@
+# ===========================================================================
+# http://www.gnu.org/software/autoconf-archive/ax_check_link_flag.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_CHECK_LINK_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS])
+#
+# DESCRIPTION
+#
+# Check whether the given FLAG works with the linker or gives an error.
+# (Warnings, however, are ignored)
+#
+# ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on
+# success/failure.
+#
+# If EXTRA-FLAGS is defined, it is added to the linker's default flags
+# when the check is done. The check is thus made with the flags: "LDFLAGS
+# EXTRA-FLAGS FLAG". This can for example be used to force the linker to
+# issue an error when a bad flag is given.
+#
+# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this
+# macro in sync with AX_CHECK_{PREPROC,COMPILE}_FLAG.
+#
+# LICENSE
+#
+# Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de>
+# Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+#
+# As a special exception, the respective Autoconf Macro's copyright owner
+# gives unlimited permission to copy, distribute and modify the configure
+# scripts that are the output of Autoconf when processing the Macro. You
+# need not follow the terms of the GNU General Public License when using
+# or distributing such scripts, even though portions of the text of the
+# Macro appear in them. The GNU General Public License (GPL) does govern
+# all other use of the material that constitutes the Autoconf Macro.
+#
+# This special exception to the GPL applies to versions of the Autoconf
+# Macro released by the Autoconf Archive. When you make and distribute a
+# modified version of the Autoconf Macro, you may extend this special
+# exception to the GPL to apply to your modified version as well.
+
+#serial 2
+
+AC_DEFUN([AX_CHECK_LINK_FLAG],
+[AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_ldflags_$4_$1])dnl
+AC_CACHE_CHECK([whether the linker accepts $1], CACHEVAR, [
+ ax_check_save_flags=$LDFLAGS
+ LDFLAGS="$LDFLAGS $4 $1"
+ AC_LINK_IFELSE([AC_LANG_PROGRAM()],
+ [AS_VAR_SET(CACHEVAR,[yes])],
+ [AS_VAR_SET(CACHEVAR,[no])])
+ LDFLAGS=$ax_check_save_flags])
+AS_IF([test x"AS_VAR_GET(CACHEVAR)" = xyes],
+ [m4_default([$2], :)],
+ [m4_default([$3], :)])
+AS_VAR_POPDEF([CACHEVAR])dnl
+])dnl AX_CHECK_LINK_FLAGS
diff --git a/m4/ax_cxx_compile_stdcxx_0x.m4 b/m4/ax_cxx_compile_stdcxx_0x.m4
new file mode 100644
index 000000000..a4e556ff9
--- /dev/null
+++ b/m4/ax_cxx_compile_stdcxx_0x.m4
@@ -0,0 +1,107 @@
+# ============================================================================
+# http://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx_0x.html
+# ============================================================================
+#
+# SYNOPSIS
+#
+# AX_CXX_COMPILE_STDCXX_0X
+#
+# DESCRIPTION
+#
+# Check for baseline language coverage in the compiler for the C++0x
+# standard.
+#
+# LICENSE
+#
+# Copyright (c) 2008 Benjamin Kosnik <bkoz@redhat.com>
+#
+# Copying and distribution of this file, with or without modification, are
+# permitted in any medium without royalty provided the copyright notice
+# and this notice are preserved. This file is offered as-is, without any
+# warranty.
+
+#serial 7
+
+AU_ALIAS([AC_CXX_COMPILE_STDCXX_0X], [AX_CXX_COMPILE_STDCXX_0X])
+AC_DEFUN([AX_CXX_COMPILE_STDCXX_0X], [
+ AC_CACHE_CHECK(if g++ supports C++0x features without additional flags,
+ ax_cv_cxx_compile_cxx0x_native,
+ [AC_LANG_SAVE
+ AC_LANG_CPLUSPLUS
+ AC_TRY_COMPILE([
+ template <typename T>
+ struct check
+ {
+ static_assert(sizeof(int) <= sizeof(T), "not big enough");
+ };
+
+ typedef check<check<bool>> right_angle_brackets;
+
+ int a;
+ decltype(a) b;
+
+ typedef check<int> check_type;
+ check_type c;
+ check_type&& cr = static_cast<check_type&&>(c);],,
+ ax_cv_cxx_compile_cxx0x_native=yes, ax_cv_cxx_compile_cxx0x_native=no)
+ AC_LANG_RESTORE
+ ])
+
+ AC_CACHE_CHECK(if g++ supports C++0x features with -std=c++0x,
+ ax_cv_cxx_compile_cxx0x_cxx,
+ [AC_LANG_SAVE
+ AC_LANG_CPLUSPLUS
+ ac_save_CXXFLAGS="$CXXFLAGS"
+ CXXFLAGS="$CXXFLAGS -std=c++0x"
+ AC_TRY_COMPILE([
+ template <typename T>
+ struct check
+ {
+ static_assert(sizeof(int) <= sizeof(T), "not big enough");
+ };
+
+ typedef check<check<bool>> right_angle_brackets;
+
+ int a;
+ decltype(a) b;
+
+ typedef check<int> check_type;
+ check_type c;
+ check_type&& cr = static_cast<check_type&&>(c);],,
+ ax_cv_cxx_compile_cxx0x_cxx=yes, ax_cv_cxx_compile_cxx0x_cxx=no)
+ CXXFLAGS="$ac_save_CXXFLAGS"
+ AC_LANG_RESTORE
+ ])
+
+ AC_CACHE_CHECK(if g++ supports C++0x features with -std=gnu++0x,
+ ax_cv_cxx_compile_cxx0x_gxx,
+ [AC_LANG_SAVE
+ AC_LANG_CPLUSPLUS
+ ac_save_CXXFLAGS="$CXXFLAGS"
+ CXXFLAGS="$CXXFLAGS -std=gnu++0x"
+ AC_TRY_COMPILE([
+ template <typename T>
+ struct check
+ {
+ static_assert(sizeof(int) <= sizeof(T), "not big enough");
+ };
+
+ typedef check<check<bool>> right_angle_brackets;
+
+ int a;
+ decltype(a) b;
+
+ typedef check<int> check_type;
+ check_type c;
+ check_type&& cr = static_cast<check_type&&>(c);],,
+ ax_cv_cxx_compile_cxx0x_gxx=yes, ax_cv_cxx_compile_cxx0x_gxx=no)
+ CXXFLAGS="$ac_save_CXXFLAGS"
+ AC_LANG_RESTORE
+ ])
+
+ if test "$ax_cv_cxx_compile_cxx0x_native" = yes ||
+ test "$ax_cv_cxx_compile_cxx0x_cxx" = yes ||
+ test "$ax_cv_cxx_compile_cxx0x_gxx" = yes; then
+ AC_DEFINE(HAVE_STDCXX_0X,,[Define if g++ supports C++0x features. ])
+ fi
+])
diff --git a/m4/faad.m4 b/m4/faad.m4
index 1048c566c..5ca520e79 100644
--- a/m4/faad.m4
+++ b/m4/faad.m4
@@ -8,38 +8,14 @@ AC_ARG_ENABLE(aac,
[disable AAC support (default: enable)]),,
enable_aac=yes)
-AC_ARG_WITH(faad,
- AS_HELP_STRING([--with-faad=PFX],
- [prefix where faad2 is installed (optional)]),,
- faad_prefix="")
-AC_ARG_WITH(faad-libraries,
- AS_HELP_STRING([--with-faad-libraries=DIR],
- [directory where faad2 library is installed (optional)]),,
- faad_libraries="")
-AC_ARG_WITH(faad-includes,
- AS_HELP_STRING([--with-faad-includes=DIR],
- [directory where faad2 header files are installed (optional)]),,
- faad_includes="")
-
if test x$enable_aac = xyes; then
- if test "x$faad_libraries" != "x" ; then
- FAAD_LIBS="-L$faad_libraries"
- elif test "x$faad_prefix" != "x" ; then
- FAAD_LIBS="-L$faad_prefix/lib"
- fi
-
- FAAD_LIBS="$FAAD_LIBS -lfaad"
-
- if test "x$faad_includes" != "x" ; then
- FAAD_CFLAGS="-I$faad_includes"
- elif test "x$faad_prefix" != "x" ; then
- FAAD_CFLAGS="-I$faad_prefix/include"
- fi
+ FAAD_LIBS="-lfaad"
+ FAAD_CFLAGS=""
oldcflags=$CFLAGS
oldlibs=$LIBS
oldcppflags=$CPPFLAGS
- CFLAGS="$CFLAGS $FAAD_CFLAGS -I."
+ CFLAGS="$CFLAGS $FAAD_CFLAGS"
LIBS="$LIBS $FAAD_LIBS"
CPPFLAGS=$CFLAGS
AC_CHECK_HEADER(faad.h,,enable_aac=no)
@@ -47,77 +23,36 @@ if test x$enable_aac = xyes; then
AC_CHECK_DECL(FAAD2_VERSION,,enable_aac=no,[#include <faad.h>])
fi
if test x$enable_aac = xyes; then
- AC_CHECK_DECL(faacDecInit2,,enable_aac=no,[#include <faad.h>])
- fi
- if test x$enable_aac = xyes; then
- AC_CHECK_LIB(faad,faacDecInit2,,enable_aac=no)
- if test x$enable_aac = xno; then
- enable_aac=yes
- AC_CHECK_LIB(faad,NeAACDecInit2,,enable_aac=no)
- fi
+ AC_CHECK_LIB(faad,NeAACDecInit2,,enable_aac=no)
fi
if test x$enable_aac = xyes; then
- AC_MSG_CHECKING(that FAAD2 uses buffer and bufferlen)
- AC_COMPILE_IFELSE([AC_LANG_SOURCE([
-#include <faad.h>
-
-int main() {
- char buffer;
- long bufferlen = 0;
- faacDecHandle decoder;
- faacDecFrameInfo frameInfo;
- faacDecConfigurationPtr config;
- unsigned char channels;
- long sampleRate;
- mp4AudioSpecificConfig mp4ASC;
-
- decoder = faacDecOpen();
- config = faacDecGetCurrentConfiguration(decoder);
- config->outputFormat = FAAD_FMT_16BIT;
- faacDecSetConfiguration(decoder,config);
- AudioSpecificConfig(&buffer, bufferlen, &mp4ASC);
- faacDecInit(decoder,&buffer,bufferlen,&sampleRate,&channels);
- faacDecInit2(decoder,&buffer,bufferlen,&sampleRate,&channels);
- faacDecDecode(decoder,&frameInfo,&buffer,bufferlen);
-
- return 0;
-}
-])],[AC_MSG_RESULT(yes);AC_DEFINE(HAVE_FAAD_BUFLEN_FUNCS,1,[Define if FAAD2 uses buflen in function calls])],[AC_MSG_RESULT(no);
AC_MSG_CHECKING(that FAAD2 can even be used)
AC_COMPILE_IFELSE([AC_LANG_SOURCE([
#include <faad.h>
int main() {
char buffer;
- faacDecHandle decoder;
- faacDecFrameInfo frameInfo;
- faacDecConfigurationPtr config;
+ NeAACDecHandle decoder;
+ NeAACDecFrameInfo frameInfo;
+ NeAACDecConfigurationPtr config;
unsigned char channels;
long sampleRate;
long bufferlen = 0;
- unsigned long dummy1_32;
- unsigned char dummy2_8, dummy3_8, dummy4_8, dummy5_8, dummy6_8,
- dummy7_8, dummy8_8;
- decoder = faacDecOpen();
- config = faacDecGetCurrentConfiguration(decoder);
+ decoder = NeAACDecOpen();
+ config = NeAACDecGetCurrentConfiguration(decoder);
config->outputFormat = FAAD_FMT_16BIT;
- faacDecSetConfiguration(decoder,config);
- AudioSpecificConfig(&buffer,&dummy1_32,&dummy2_8,
- &dummy3_8,&dummy4_8,&dummy5_8,
- &dummy6_8,&dummy7_8,&dummy8_8);
- faacDecInit(decoder,&buffer,&sampleRate,&channels);
- faacDecInit2(decoder,&buffer,bufferlen,&sampleRate,&channels);
- faacDecDecode(decoder,&frameInfo,&buffer);
- faacDecClose(decoder);
+ NeAACDecSetConfiguration(decoder,config);
+ NeAACDecInit(decoder,&buffer,bufferlen,&sampleRate,&channels);
+ NeAACDecInit2(decoder,&buffer,bufferlen,&sampleRate,&channels);
+ NeAACDecDecode(decoder,&frameInfo,&buffer,bufferlen);
+ NeAACDecClose(decoder);
return 0;
}
])],AC_MSG_RESULT(yes),[AC_MSG_RESULT(no);enable_aac=no])
- ])
fi
if test x$enable_aac = xyes; then
- AC_CHECK_MEMBERS([faacDecConfiguration.downMatrix,faacDecConfiguration.dontUpSampleImplicitSBR,faacDecFrameInfo.samplerate],,,[#include <faad.h>])
AC_DEFINE(HAVE_FAAD,1,[Define to use FAAD2 for AAC decoding])
else
AC_MSG_WARN([faad2 lib needed for MP4/AAC support -- disabling MP4/AAC support])
@@ -145,7 +80,7 @@ int main() {
unsigned char channels;
uint32_t sample_rate;
- faacDecInit2(NULL, NULL, 0, &sample_rate, &channels);
+ NeAACDecInit2(NULL, NULL, 0, &sample_rate, &channels);
return 0;
}
])],
@@ -156,40 +91,9 @@ int main() {
CFLAGS=$oldcflags
LIBS=$oldlibs
CPPFLAGS=$oldcppflags
-fi
-
-if test x$enable_aac = xyes; then
- enable_mp4=yes
- MP4FF_LIBS="-lmp4ff"
-
- oldcflags=$CFLAGS
- oldlibs=$LIBS
- oldcppflags=$CPPFLAGS
- CFLAGS="$CFLAGS $FAAD_CFLAGS"
- LIBS="$LIBS $FAAD_LIBS $MP4FF_LIBS"
- CPPFLAGS=$CFLAGS
-
- AC_CHECK_HEADER(mp4ff.h,,enable_mp4=no)
-
- if test x$enable_mp4 = xyes; then
- AC_CHECK_LIB(mp4ff,mp4ff_open_read,,enable_mp4=no)
- fi
-
- if test x$enable_mp4 = xyes; then
- AC_SUBST(MP4FF_LIBS)
- AC_DEFINE(HAVE_MP4, 1, [Define to use FAAD2+mp4ff for MP4 decoding])
- else
- AC_MSG_WARN([libmp4ff needed for MP4 support -- disabling MP4 support])
- unset MP4FF_LIBS
- fi
-
- CFLAGS=$oldcflags
- LIBS=$oldlibs
- CPPFLAGS=$oldcppflags
else
- enable_mp4=no
- FAAD_CFLAGS=""
FAAD_LIBS=""
+ FAAD_CFLAGS=""
fi
AC_SUBST(FAAD_CFLAGS)
diff --git a/scripts/makedist.sh b/scripts/makedist.sh
deleted file mode 100755
index 7f8624d8f..000000000
--- a/scripts/makedist.sh
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/bin/sh
-PWD=`pwd`
-
-## If we're not in the scripts directory
-## assume the base directory.
-if test "`basename $PWD`" = "scripts"; then
- cd ../
-else
- MYOLDPWD=`pwd`
- cd `dirname $0`/../
-fi
-
-if test -e Makefile
-then
- make distclean
-fi
-./autogen.sh
-make
-make dist
-
-if test "`basename $PWD`" = "scripts"; then
- cd contrib/
-else
- cd $MYOLDPWD
-fi
diff --git a/scripts/mpd-indent.sh b/scripts/mpd-indent.sh
deleted file mode 100755
index 0bf54189a..000000000
--- a/scripts/mpd-indent.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/sh
-indent -npro -kr -i8 -ts8 -sob -l80 -ss -ncs -cdw -cd0 -c0 -cp0 "$@"
-
-# there doesn't seem to be an indent switch for this, but this
-# forces goto labels to the left-most column, without indentation
-perl -i -p -e 's/^\s+(\w+):/$1:/g unless /^\s+default:/' "$@"
diff --git a/scripts/test.sh b/scripts/test.sh
deleted file mode 100755
index 739a8a6e7..000000000
--- a/scripts/test.sh
+++ /dev/null
@@ -1,93 +0,0 @@
-#!/bin/sh -e
-#
-# This shell script tests the build of MPD with various compile-time
-# options.
-#
-# Author: Max Kellermann <max@duempel.org>
-
-PREFIX=/tmp/mpd
-rm -rf $PREFIX
-
-test "x$MAKE" != x || MAKE=make
-
-export CFLAGS="-Os"
-
-test -x configure || NOCONFIGURE=1 ./autogen.sh
-
-# all features on
-./configure --prefix=$PREFIX/full \
- --disable-dependency-tracking --enable-debug --enable-werror \
- --enable-un \
- --enable-modplug \
- --enable-ao --enable-mikmod --enable-mvp
-$MAKE install
-$MAKE distclean
-
-# no UN, no oggvorbis, no flac, enable oggflac
-./configure --prefix=$PREFIX/small \
- --disable-dependency-tracking --enable-debug --enable-werror \
- --disable-un \
- --disable-flac --disable-vorbis --enable-oggflac
-$MAKE install
-$MAKE distclean
-
-# strip down (disable TCP, disable nearly all plugins)
-CFLAGS="$CFLAGS -DNDEBUG" \
-./configure --prefix=$PREFIX/tiny \
- --disable-dependency-tracking --disable-debug --enable-werror \
- --disable-tcp \
- --disable-curl \
- --disable-id3 --disable-lsr \
- --disable-ao --disable-alsa --disable-jack --disable-pulse --disable-fifo \
- --disable-shout-ogg --disable-shout-mp3 --disable-lame-encoder \
- --disable-ffmpeg --disable-wavpack --disable-mpc --disable-aac \
- --disable-flac --disable-vorbis --disable-oggflac --disable-audiofile \
- --disable-cue \
- --with-zeroconf=no
-$MAKE install
-$MAKE distclean
-
-# shout: ogg without mp3
-# sndfile instead of modplug
-./configure --prefix=$PREFIX/shout_ogg \
- --disable-dependency-tracking --disable-debug --enable-werror \
- --disable-tcp \
- --disable-curl \
- --disable-id3 --disable-lsr \
- --disable-ao --disable-alsa --disable-jack --disable-pulse --disable-fifo \
- --enable-shout-ogg --disable-shout-mp3 --disable-lame-encoder \
- --disable-ffmpeg --disable-wavpack --disable-mpc --disable-aac \
- --disable-flac --enable-vorbis --disable-oggflac --disable-audiofile \
- --disable-modplug --enable-sndfile \
- --with-zeroconf=no
-$MAKE install
-$MAKE distclean
-
-# shout: mp3 without ogg
-./configure --prefix=$PREFIX/shout_mp3 \
- --disable-dependency-tracking --disable-debug --enable-werror \
- --disable-tcp \
- --disable-curl \
- --disable-id3 --disable-lsr \
- --disable-ao --disable-alsa --disable-jack --disable-pulse --disable-fifo \
- --disable-shout-ogg --enable-shout-mp3 --enable-lame-encoder \
- --disable-ffmpeg --disable-wavpack --disable-mpc --disable-aac \
- --disable-flac --disable-vorbis --disable-oggflac --disable-audiofile \
- --with-zeroconf=no
-$MAKE install
-$MAKE distclean
-
-# oggvorbis + oggflac
-./configure --prefix=$PREFIX/oggvorbisflac \
- --disable-dependency-tracking --disable-debug --enable-werror \
- --disable-tcp \
- --disable-curl \
- --disable-id3 --disable-lsr \
- --disable-mp3 \
- --disable-ao --disable-alsa --disable-jack --disable-pulse --disable-fifo \
- --disable-shout-ogg --disable-shout-mp3 --disable-lame-encoder \
- --disable-ffmpeg --disable-wavpack --disable-mpc --disable-aac \
- --disable-flac --enable-vorbis --enable-oggflac --disable-audiofile \
- --with-zeroconf=no
-$MAKE install
-$MAKE distclean
diff --git a/src/AllCommands.cxx b/src/AllCommands.cxx
new file mode 100644
index 000000000..8f651ec04
--- /dev/null
+++ b/src/AllCommands.cxx
@@ -0,0 +1,385 @@
+/*
+ * 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 "AllCommands.hxx"
+#include "command.h"
+#include "QueueCommands.hxx"
+#include "PlayerCommands.hxx"
+#include "PlaylistCommands.hxx"
+#include "DatabaseCommands.hxx"
+#include "OutputCommands.hxx"
+#include "MessageCommands.hxx"
+#include "OtherCommands.hxx"
+#include "Permission.hxx"
+#include "Tag.hxx"
+#include "protocol/Result.hxx"
+#include "Client.hxx"
+#include "util/Tokenizer.hxx"
+
+#ifdef ENABLE_SQLITE
+#include "StickerCommands.hxx"
+#include "StickerDatabase.hxx"
+#endif
+
+#include <assert.h>
+#include <string.h>
+
+/*
+ * The most we ever use is for search/find, and that limits it to the
+ * number of tags we can have. Add one for the command, and one extra
+ * to catch errors clients may send us
+ */
+#define COMMAND_ARGV_MAX (2+(TAG_NUM_OF_ITEM_TYPES*2))
+
+/* if min: -1 don't check args *
+ * if max: -1 no max args */
+struct command {
+ const char *cmd;
+ unsigned permission;
+ int min;
+ int max;
+ enum command_return (*handler)(Client *client, int argc, char **argv);
+};
+
+/* don't be fooled, this is the command handler for "commands" command */
+static enum command_return
+handle_commands(Client *client,
+ G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]);
+
+static enum command_return
+handle_not_commands(Client *client,
+ G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]);
+
+/**
+ * The command registry.
+ *
+ * This array must be sorted!
+ */
+static const struct command commands[] = {
+ { "add", PERMISSION_ADD, 1, 1, handle_add },
+ { "addid", PERMISSION_ADD, 1, 2, handle_addid },
+ { "channels", PERMISSION_READ, 0, 0, handle_channels },
+ { "clear", PERMISSION_CONTROL, 0, 0, handle_clear },
+ { "clearerror", PERMISSION_CONTROL, 0, 0, handle_clearerror },
+ { "close", PERMISSION_NONE, -1, -1, handle_close },
+ { "commands", PERMISSION_NONE, 0, 0, handle_commands },
+ { "config", PERMISSION_ADMIN, 0, 0, handle_config },
+ { "consume", PERMISSION_CONTROL, 1, 1, handle_consume },
+ { "count", PERMISSION_READ, 2, -1, handle_count },
+ { "crossfade", PERMISSION_CONTROL, 1, 1, handle_crossfade },
+ { "currentsong", PERMISSION_READ, 0, 0, handle_currentsong },
+ { "decoders", PERMISSION_READ, 0, 0, handle_decoders },
+ { "delete", PERMISSION_CONTROL, 1, 1, handle_delete },
+ { "deleteid", PERMISSION_CONTROL, 1, 1, handle_deleteid },
+ { "disableoutput", PERMISSION_ADMIN, 1, 1, handle_disableoutput },
+ { "enableoutput", PERMISSION_ADMIN, 1, 1, handle_enableoutput },
+ { "find", PERMISSION_READ, 2, -1, handle_find },
+ { "findadd", PERMISSION_READ, 2, -1, handle_findadd},
+ { "idle", PERMISSION_READ, 0, -1, handle_idle },
+ { "kill", PERMISSION_ADMIN, -1, -1, handle_kill },
+ { "list", PERMISSION_READ, 1, -1, handle_list },
+ { "listall", PERMISSION_READ, 0, 1, handle_listall },
+ { "listallinfo", PERMISSION_READ, 0, 1, handle_listallinfo },
+ { "listplaylist", PERMISSION_READ, 1, 1, handle_listplaylist },
+ { "listplaylistinfo", PERMISSION_READ, 1, 1, handle_listplaylistinfo },
+ { "listplaylists", PERMISSION_READ, 0, 0, handle_listplaylists },
+ { "load", PERMISSION_ADD, 1, 2, handle_load },
+ { "lsinfo", PERMISSION_READ, 0, 1, handle_lsinfo },
+ { "mixrampdb", PERMISSION_CONTROL, 1, 1, handle_mixrampdb },
+ { "mixrampdelay", PERMISSION_CONTROL, 1, 1, handle_mixrampdelay },
+ { "move", PERMISSION_CONTROL, 2, 2, handle_move },
+ { "moveid", PERMISSION_CONTROL, 2, 2, handle_moveid },
+ { "next", PERMISSION_CONTROL, 0, 0, handle_next },
+ { "notcommands", PERMISSION_NONE, 0, 0, handle_not_commands },
+ { "outputs", PERMISSION_READ, 0, 0, handle_devices },
+ { "password", PERMISSION_NONE, 1, 1, handle_password },
+ { "pause", PERMISSION_CONTROL, 0, 1, handle_pause },
+ { "ping", PERMISSION_NONE, 0, 0, handle_ping },
+ { "play", PERMISSION_CONTROL, 0, 1, handle_play },
+ { "playid", PERMISSION_CONTROL, 0, 1, handle_playid },
+ { "playlist", PERMISSION_READ, 0, 0, handle_playlist },
+ { "playlistadd", PERMISSION_CONTROL, 2, 2, handle_playlistadd },
+ { "playlistclear", PERMISSION_CONTROL, 1, 1, handle_playlistclear },
+ { "playlistdelete", PERMISSION_CONTROL, 2, 2, handle_playlistdelete },
+ { "playlistfind", PERMISSION_READ, 2, -1, handle_playlistfind },
+ { "playlistid", PERMISSION_READ, 0, 1, handle_playlistid },
+ { "playlistinfo", PERMISSION_READ, 0, 1, handle_playlistinfo },
+ { "playlistmove", PERMISSION_CONTROL, 3, 3, handle_playlistmove },
+ { "playlistsearch", PERMISSION_READ, 2, -1, handle_playlistsearch },
+ { "plchanges", PERMISSION_READ, 1, 1, handle_plchanges },
+ { "plchangesposid", PERMISSION_READ, 1, 1, handle_plchangesposid },
+ { "previous", PERMISSION_CONTROL, 0, 0, handle_previous },
+ { "prio", PERMISSION_CONTROL, 2, -1, handle_prio },
+ { "prioid", PERMISSION_CONTROL, 2, -1, handle_prioid },
+ { "random", PERMISSION_CONTROL, 1, 1, handle_random },
+ { "readmessages", PERMISSION_READ, 0, 0, handle_read_messages },
+ { "rename", PERMISSION_CONTROL, 2, 2, handle_rename },
+ { "repeat", PERMISSION_CONTROL, 1, 1, handle_repeat },
+ { "replay_gain_mode", PERMISSION_CONTROL, 1, 1,
+ handle_replay_gain_mode },
+ { "replay_gain_status", PERMISSION_READ, 0, 0,
+ handle_replay_gain_status },
+ { "rescan", PERMISSION_CONTROL, 0, 1, handle_rescan },
+ { "rm", PERMISSION_CONTROL, 1, 1, handle_rm },
+ { "save", PERMISSION_CONTROL, 1, 1, handle_save },
+ { "search", PERMISSION_READ, 2, -1, handle_search },
+ { "searchadd", PERMISSION_ADD, 2, -1, handle_searchadd },
+ { "searchaddpl", PERMISSION_CONTROL, 3, -1, handle_searchaddpl },
+ { "seek", PERMISSION_CONTROL, 2, 2, handle_seek },
+ { "seekcur", PERMISSION_CONTROL, 1, 1, handle_seekcur },
+ { "seekid", PERMISSION_CONTROL, 2, 2, handle_seekid },
+ { "sendmessage", PERMISSION_CONTROL, 2, 2, handle_send_message },
+ { "setvol", PERMISSION_CONTROL, 1, 1, handle_setvol },
+ { "shuffle", PERMISSION_CONTROL, 0, 1, handle_shuffle },
+ { "single", PERMISSION_CONTROL, 1, 1, handle_single },
+ { "stats", PERMISSION_READ, 0, 0, handle_stats },
+ { "status", PERMISSION_READ, 0, 0, handle_status },
+#ifdef ENABLE_SQLITE
+ { "sticker", PERMISSION_ADMIN, 3, -1, handle_sticker },
+#endif
+ { "stop", PERMISSION_CONTROL, 0, 0, handle_stop },
+ { "subscribe", PERMISSION_READ, 1, 1, handle_subscribe },
+ { "swap", PERMISSION_CONTROL, 2, 2, handle_swap },
+ { "swapid", PERMISSION_CONTROL, 2, 2, handle_swapid },
+ { "tagtypes", PERMISSION_READ, 0, 0, handle_tagtypes },
+ { "unsubscribe", PERMISSION_READ, 1, 1, handle_unsubscribe },
+ { "update", PERMISSION_CONTROL, 0, 1, handle_update },
+ { "urlhandlers", PERMISSION_READ, 0, 0, handle_urlhandlers },
+};
+
+static const unsigned num_commands = sizeof(commands) / sizeof(commands[0]);
+
+static bool
+command_available(G_GNUC_UNUSED const struct command *cmd)
+{
+#ifdef ENABLE_SQLITE
+ if (strcmp(cmd->cmd, "sticker") == 0)
+ return sticker_enabled();
+#endif
+
+ return true;
+}
+
+/* don't be fooled, this is the command handler for "commands" command */
+static enum command_return
+handle_commands(Client *client,
+ G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
+{
+ const unsigned permission = client_get_permission(client);
+ const struct command *cmd;
+
+ for (unsigned i = 0; i < num_commands; ++i) {
+ cmd = &commands[i];
+
+ if (cmd->permission == (permission & cmd->permission) &&
+ command_available(cmd))
+ client_printf(client, "command: %s\n", cmd->cmd);
+ }
+
+ return COMMAND_RETURN_OK;
+}
+
+static enum command_return
+handle_not_commands(Client *client,
+ G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
+{
+ const unsigned permission = client_get_permission(client);
+ const struct command *cmd;
+
+ for (unsigned i = 0; i < num_commands; ++i) {
+ cmd = &commands[i];
+
+ if (cmd->permission != (permission & cmd->permission))
+ client_printf(client, "command: %s\n", cmd->cmd);
+ }
+
+ return COMMAND_RETURN_OK;
+}
+
+void command_init(void)
+{
+#ifndef NDEBUG
+ /* ensure that the command list is sorted */
+ for (unsigned i = 0; i < num_commands - 1; ++i)
+ assert(strcmp(commands[i].cmd, commands[i + 1].cmd) < 0);
+#endif
+}
+
+void command_finish(void)
+{
+}
+
+static const struct command *
+command_lookup(const char *name)
+{
+ unsigned a = 0, b = num_commands, i;
+ int cmp;
+
+ /* binary search */
+ do {
+ i = (a + b) / 2;
+
+ cmp = strcmp(name, commands[i].cmd);
+ if (cmp == 0)
+ return &commands[i];
+ else if (cmp < 0)
+ b = i;
+ else if (cmp > 0)
+ a = i + 1;
+ } while (a < b);
+
+ return NULL;
+}
+
+static bool
+command_check_request(const struct command *cmd, Client *client,
+ unsigned permission, int argc, char *argv[])
+{
+ int min = cmd->min + 1;
+ int max = cmd->max + 1;
+
+ if (cmd->permission != (permission & cmd->permission)) {
+ if (client != NULL)
+ command_error(client, ACK_ERROR_PERMISSION,
+ "you don't have permission for \"%s\"",
+ cmd->cmd);
+ return false;
+ }
+
+ if (min == 0)
+ return true;
+
+ if (min == max && max != argc) {
+ if (client != NULL)
+ command_error(client, ACK_ERROR_ARG,
+ "wrong number of arguments for \"%s\"",
+ argv[0]);
+ return false;
+ } else if (argc < min) {
+ if (client != NULL)
+ command_error(client, ACK_ERROR_ARG,
+ "too few arguments for \"%s\"", argv[0]);
+ return false;
+ } else if (argc > max && max /* != 0 */ ) {
+ if (client != NULL)
+ command_error(client, ACK_ERROR_ARG,
+ "too many arguments for \"%s\"", argv[0]);
+ return false;
+ } else
+ return true;
+}
+
+static const struct command *
+command_checked_lookup(Client *client, unsigned permission,
+ int argc, char *argv[])
+{
+ const struct command *cmd;
+
+ current_command = "";
+
+ if (argc == 0)
+ return NULL;
+
+ cmd = command_lookup(argv[0]);
+ if (cmd == NULL) {
+ if (client != NULL)
+ command_error(client, ACK_ERROR_UNKNOWN,
+ "unknown command \"%s\"", argv[0]);
+ return NULL;
+ }
+
+ current_command = cmd->cmd;
+
+ if (!command_check_request(cmd, client, permission, argc, argv))
+ return NULL;
+
+ return cmd;
+}
+
+enum command_return
+command_process(Client *client, unsigned num, char *line)
+{
+ GError *error = NULL;
+ int argc;
+ char *argv[COMMAND_ARGV_MAX] = { NULL };
+ const struct command *cmd;
+ enum command_return ret = COMMAND_RETURN_ERROR;
+
+ command_list_num = num;
+
+ /* get the command name (first word on the line) */
+
+ Tokenizer tokenizer(line);
+ argv[0] = tokenizer.NextWord(&error);
+ if (argv[0] == NULL) {
+ current_command = "";
+ if (tokenizer.IsEnd())
+ command_error(client, ACK_ERROR_UNKNOWN,
+ "No command given");
+ else {
+ command_error(client, ACK_ERROR_UNKNOWN,
+ "%s", error->message);
+ g_error_free(error);
+ }
+ current_command = NULL;
+
+ return COMMAND_RETURN_ERROR;
+ }
+
+ argc = 1;
+
+ /* now parse the arguments (quoted or unquoted) */
+
+ while (argc < (int)G_N_ELEMENTS(argv) &&
+ (argv[argc] =
+ tokenizer.NextParam(&error)) != NULL)
+ ++argc;
+
+ /* some error checks; we have to set current_command because
+ command_error() expects it to be set */
+
+ current_command = argv[0];
+
+ if (argc >= (int)G_N_ELEMENTS(argv)) {
+ command_error(client, ACK_ERROR_ARG, "Too many arguments");
+ current_command = NULL;
+ return COMMAND_RETURN_ERROR;
+ }
+
+ if (!tokenizer.IsEnd()) {
+ command_error(client, ACK_ERROR_ARG,
+ "%s", error->message);
+ current_command = NULL;
+ g_error_free(error);
+ return COMMAND_RETURN_ERROR;
+ }
+
+ /* look up and invoke the command handler */
+
+ cmd = command_checked_lookup(client, client_get_permission(client),
+ argc, argv);
+ if (cmd)
+ ret = cmd->handler(client, argc, argv);
+
+ current_command = NULL;
+ command_list_num = 0;
+
+ return ret;
+}
diff --git a/src/AllCommands.hxx b/src/AllCommands.hxx
new file mode 100644
index 000000000..a55eb5a3b
--- /dev/null
+++ b/src/AllCommands.hxx
@@ -0,0 +1,34 @@
+/*
+ * 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_ALL_COMMANDS_HXX
+#define MPD_ALL_COMMANDS_HXX
+
+#include "command.h"
+
+class Client;
+
+void command_init(void);
+
+void command_finish(void);
+
+enum command_return
+command_process(Client *client, unsigned num, char *line);
+
+#endif
diff --git a/src/ApeLoader.cxx b/src/ApeLoader.cxx
new file mode 100644
index 000000000..dfbdb4ef3
--- /dev/null
+++ b/src/ApeLoader.cxx
@@ -0,0 +1,115 @@
+/*
+ * 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 "ApeLoader.hxx"
+
+#include <glib.h>
+
+#include <stdint.h>
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+struct ape_footer {
+ unsigned char id[8];
+ uint32_t version;
+ uint32_t length;
+ uint32_t count;
+ unsigned char flags[4];
+ unsigned char reserved[8];
+};
+
+static bool
+ape_scan_internal(FILE *fp, ApeTagCallback callback)
+{
+ /* determine if file has an apeV2 tag */
+ struct ape_footer footer;
+ if (fseek(fp, -(long)sizeof(footer), SEEK_END) ||
+ fread(&footer, 1, sizeof(footer), fp) != sizeof(footer) ||
+ memcmp(footer.id, "APETAGEX", sizeof(footer.id)) != 0 ||
+ GUINT32_FROM_LE(footer.version) != 2000)
+ return false;
+
+ /* find beginning of ape tag */
+ size_t remaining = GUINT32_FROM_LE(footer.length);
+ if (remaining <= sizeof(footer) + 10 ||
+ /* refuse to load more than one megabyte of tag data */
+ remaining > 1024 * 1024 ||
+ fseek(fp, -(long)remaining, SEEK_END))
+ return false;
+
+ /* read tag into buffer */
+ remaining -= sizeof(footer);
+ assert(remaining > 10);
+
+ char *buffer = (char *)g_malloc(remaining);
+ if (fread(buffer, 1, remaining, fp) != remaining) {
+ g_free(buffer);
+ return false;
+ }
+
+ /* read tags */
+ unsigned n = GUINT32_FROM_LE(footer.count);
+ const char *p = buffer;
+ while (n-- && remaining > 10) {
+ size_t size = GUINT32_FROM_LE(*(const uint32_t *)p);
+ p += 4;
+ remaining -= 4;
+ unsigned long flags = GUINT32_FROM_LE(*(const uint32_t *)p);
+ p += 4;
+ remaining -= 4;
+
+ /* get the key */
+ const char *key = p;
+ while (remaining > size && *p != '\0') {
+ p++;
+ remaining--;
+ }
+ p++;
+ remaining--;
+
+ /* get the value */
+ if (remaining < size)
+ break;
+
+ if (!callback(flags, key, p, size))
+ break;
+
+ p += size;
+ remaining -= size;
+ }
+
+ g_free(buffer);
+ return true;
+}
+
+bool
+tag_ape_scan(const char *path_fs, ApeTagCallback callback)
+{
+ FILE *fp;
+
+ fp = fopen(path_fs, "rb");
+ if (fp == nullptr)
+ return false;
+
+ bool success = ape_scan_internal(fp, callback);
+ fclose(fp);
+ return success;
+}
diff --git a/src/ApeLoader.hxx b/src/ApeLoader.hxx
new file mode 100644
index 000000000..a32ab840c
--- /dev/null
+++ b/src/ApeLoader.hxx
@@ -0,0 +1,43 @@
+/*
+ * 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_APE_LOADER_HXX
+#define MPD_APE_LOADER_HXX
+
+#include "check.h"
+
+#include <functional>
+
+#include <stddef.h>
+
+typedef std::function<bool(unsigned long flags, const char *key,
+ const char *value,
+ size_t value_length)> ApeTagCallback;
+
+/**
+ * Scans the APE tag values from a file.
+ *
+ * @param path_fs the path of the file in filesystem encoding
+ * @return false if the file could not be opened or if no APE tag is
+ * present
+ */
+bool
+tag_ape_scan(const char *path_fs, ApeTagCallback callback);
+
+#endif
diff --git a/src/ApeReplayGain.cxx b/src/ApeReplayGain.cxx
new file mode 100644
index 000000000..0135d1b61
--- /dev/null
+++ b/src/ApeReplayGain.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 "ApeReplayGain.hxx"
+#include "ApeLoader.hxx"
+#include "replay_gain_info.h"
+
+#include <glib.h>
+
+#include <string.h>
+#include <stdlib.h>
+
+static bool
+replay_gain_ape_callback(unsigned long flags, const char *key,
+ const char *_value, size_t value_length,
+ struct replay_gain_info *info)
+{
+ /* we only care about utf-8 text tags */
+ if ((flags & (0x3 << 1)) != 0)
+ return false;
+
+ char value[16];
+ if (value_length >= sizeof(value))
+ return false;
+
+ memcpy(value, _value, value_length);
+ value[value_length] = 0;
+
+ if (g_ascii_strcasecmp(key, "replaygain_track_gain") == 0) {
+ info->tuples[REPLAY_GAIN_TRACK].gain = atof(value);
+ return true;
+ } else if (g_ascii_strcasecmp(key, "replaygain_album_gain") == 0) {
+ info->tuples[REPLAY_GAIN_ALBUM].gain = atof(value);
+ return true;
+ } else if (g_ascii_strcasecmp(key, "replaygain_track_peak") == 0) {
+ info->tuples[REPLAY_GAIN_TRACK].peak = atof(value);
+ return true;
+ } else if (g_ascii_strcasecmp(key, "replaygain_album_peak") == 0) {
+ info->tuples[REPLAY_GAIN_ALBUM].peak = atof(value);
+ return true;
+ } else
+ return false;
+}
+
+bool
+replay_gain_ape_read(const char *path_fs, struct replay_gain_info *info)
+{
+ bool found = false;
+
+ auto callback = [info, &found]
+ (unsigned long flags, const char *key,
+ const char *value,
+ size_t value_length) {
+ found |= replay_gain_ape_callback(flags, key,
+ value, value_length,
+ info);
+ return true;
+ };
+
+ return tag_ape_scan(path_fs, callback) && found;
+}
diff --git a/src/ApeReplayGain.hxx b/src/ApeReplayGain.hxx
new file mode 100644
index 000000000..4bc34a9d2
--- /dev/null
+++ b/src/ApeReplayGain.hxx
@@ -0,0 +1,30 @@
+/*
+ * 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_APE_REPLAY_GAIN_HXX
+#define MPD_APE_REPLAY_GAIN_HXX
+
+#include "check.h"
+
+struct replay_gain_info;
+
+bool
+replay_gain_ape_read(const char *path_fs, struct replay_gain_info *info);
+
+#endif
diff --git a/src/ApeTag.cxx b/src/ApeTag.cxx
new file mode 100644
index 000000000..34c2b703b
--- /dev/null
+++ b/src/ApeTag.cxx
@@ -0,0 +1,104 @@
+/*
+ * 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 "ApeTag.hxx"
+#include "ApeLoader.hxx"
+#include "Tag.hxx"
+#include "TagTable.hxx"
+#include "TagHandler.hxx"
+
+const struct tag_table ape_tags[] = {
+ { "album artist", TAG_ALBUM_ARTIST },
+ { "year", TAG_DATE },
+ { nullptr, TAG_NUM_OF_ITEM_TYPES }
+};
+
+static enum tag_type
+tag_ape_name_parse(const char *name)
+{
+ enum tag_type type = tag_table_lookup_i(ape_tags, name);
+ if (type == TAG_NUM_OF_ITEM_TYPES)
+ type = tag_name_parse_i(name);
+
+ return type;
+}
+
+/**
+ * @return true if the item was recognized
+ */
+static bool
+tag_ape_import_item(unsigned long flags,
+ const char *key, const char *value, size_t value_length,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ /* we only care about utf-8 text tags */
+ if ((flags & (0x3 << 1)) != 0)
+ return false;
+
+ tag_handler_invoke_pair(handler, handler_ctx, key, value);
+
+ enum tag_type type = tag_ape_name_parse(key);
+ if (type == TAG_NUM_OF_ITEM_TYPES)
+ return false;
+
+ bool recognized = false;
+ const char *end = value + value_length;
+ while (true) {
+ /* multiple values are separated by null bytes */
+ const char *n = (const char *)memchr(value, 0, end - value);
+ if (n != nullptr) {
+ if (n > value) {
+ tag_handler_invoke_tag(handler, handler_ctx,
+ type, value);
+ recognized = true;
+ }
+
+ value = n + 1;
+ } else {
+ char *p = g_strndup(value, end - value);
+ tag_handler_invoke_tag(handler, handler_ctx,
+ type, p);
+ g_free(p);
+ recognized = true;
+ break;
+ }
+ }
+
+ return recognized;
+}
+
+bool
+tag_ape_scan2(const char *path_fs,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ bool recognized = false;
+
+ auto callback = [handler, handler_ctx, &recognized]
+ (unsigned long flags, const char *key,
+ const char *value,
+ size_t value_length) {
+ recognized |= tag_ape_import_item(flags, key, value,
+ value_length,
+ handler, handler_ctx);
+ return true;
+ };
+
+ return tag_ape_scan(path_fs, callback) && recognized;
+}
diff --git a/src/ApeTag.hxx b/src/ApeTag.hxx
new file mode 100644
index 000000000..1a7143314
--- /dev/null
+++ b/src/ApeTag.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_APE_TAG_HXX
+#define MPD_APE_TAG_HXX
+
+#include "TagTable.hxx"
+
+struct tag_handler;
+
+extern const struct tag_table ape_tags[];
+
+/**
+ * Scan the APE tags of a file.
+ *
+ * @param path_fs the path of the file in filesystem encoding
+ */
+bool
+tag_ape_scan2(const char *path_fs,
+ const struct tag_handler *handler, void *handler_ctx);
+
+#endif
diff --git a/src/ArchiveFile.hxx b/src/ArchiveFile.hxx
new file mode 100644
index 000000000..c7933ebd1
--- /dev/null
+++ b/src/ArchiveFile.hxx
@@ -0,0 +1,56 @@
+/*
+ * 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_ARCHIVE_FILE_HXX
+#define MPD_ARCHIVE_FILE_HXX
+
+class ArchiveFile {
+public:
+ const struct archive_plugin &plugin;
+
+ ArchiveFile(const struct archive_plugin &_plugin)
+ :plugin(_plugin) {}
+
+protected:
+ /**
+ * Use Close() instead of delete.
+ */
+ ~ArchiveFile() {}
+
+public:
+ virtual void Close() = 0;
+
+ /**
+ * Visit all entries inside this archive.
+ */
+ virtual void Visit(ArchiveVisitor &visitor) = 0;
+
+ /**
+ * Opens an input_stream of a file within the archive.
+ *
+ * @param path the path within the archive
+ * @param error_r location to store the error occurring, or
+ * NULL to ignore errors
+ */
+ virtual input_stream *OpenStream(const char *path,
+ Mutex &mutex, Cond &cond,
+ GError **error_r) = 0;
+};
+
+#endif
diff --git a/src/ArchiveList.cxx b/src/ArchiveList.cxx
new file mode 100644
index 000000000..894e31031
--- /dev/null
+++ b/src/ArchiveList.cxx
@@ -0,0 +1,90 @@
+/*
+ * 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 "ArchiveList.hxx"
+#include "ArchivePlugin.hxx"
+#include "util/StringUtil.hxx"
+#include "archive/Bzip2ArchivePlugin.hxx"
+#include "archive/Iso9660ArchivePlugin.hxx"
+#include "archive/ZzipArchivePlugin.hxx"
+
+#include <string.h>
+#include <glib.h>
+
+const struct archive_plugin *const archive_plugins[] = {
+#ifdef HAVE_BZ2
+ &bz2_archive_plugin,
+#endif
+#ifdef HAVE_ZZIP
+ &zzip_archive_plugin,
+#endif
+#ifdef HAVE_ISO9660
+ &iso9660_archive_plugin,
+#endif
+ NULL
+};
+
+/** which plugins have been initialized successfully? */
+static bool archive_plugins_enabled[G_N_ELEMENTS(archive_plugins) - 1];
+
+#define archive_plugins_for_each_enabled(plugin) \
+ archive_plugins_for_each(plugin) \
+ if (archive_plugins_enabled[archive_plugin_iterator - archive_plugins])
+
+const struct archive_plugin *
+archive_plugin_from_suffix(const char *suffix)
+{
+ if (suffix == NULL)
+ return NULL;
+
+ archive_plugins_for_each_enabled(plugin)
+ if (plugin->suffixes != NULL &&
+ string_array_contains(plugin->suffixes, suffix))
+ return plugin;
+
+ return NULL;
+}
+
+const struct archive_plugin *
+archive_plugin_from_name(const char *name)
+{
+ archive_plugins_for_each_enabled(plugin)
+ if (strcmp(plugin->name, name) == 0)
+ return plugin;
+
+ return NULL;
+}
+
+void archive_plugin_init_all(void)
+{
+ for (unsigned i = 0; archive_plugins[i] != NULL; ++i) {
+ const struct archive_plugin *plugin = archive_plugins[i];
+ if (plugin->init == NULL || archive_plugins[i]->init())
+ archive_plugins_enabled[i] = true;
+ }
+}
+
+void archive_plugin_deinit_all(void)
+{
+ archive_plugins_for_each_enabled(plugin)
+ if (plugin->finish != NULL)
+ plugin->finish();
+}
+
diff --git a/src/ArchiveList.hxx b/src/ArchiveList.hxx
new file mode 100644
index 000000000..057c351de
--- /dev/null
+++ b/src/ArchiveList.hxx
@@ -0,0 +1,47 @@
+/*
+ * 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_ARCHIVE_LIST_HXX
+#define MPD_ARCHIVE_LIST_HXX
+
+struct archive_plugin;
+
+extern const struct archive_plugin *const archive_plugins[];
+
+#define archive_plugins_for_each(plugin) \
+ for (const struct archive_plugin *plugin, \
+ *const*archive_plugin_iterator = &archive_plugins[0]; \
+ (plugin = *archive_plugin_iterator) != NULL; \
+ ++archive_plugin_iterator)
+
+/* interface for using plugins */
+
+const struct archive_plugin *
+archive_plugin_from_suffix(const char *suffix);
+
+const struct archive_plugin *
+archive_plugin_from_name(const char *name);
+
+/* this is where we "load" all the "plugins" ;-) */
+void archive_plugin_init_all(void);
+
+/* this is where we "unload" all the "plugins" */
+void archive_plugin_deinit_all(void);
+
+#endif
diff --git a/src/ArchiveLookup.cxx b/src/ArchiveLookup.cxx
new file mode 100644
index 000000000..747f5c7e5
--- /dev/null
+++ b/src/ArchiveLookup.cxx
@@ -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.
+ */
+
+#include "config.h" /* must be first for large file support */
+#include "ArchiveLookup.hxx"
+
+#include <stdio.h>
+
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <glib.h>
+
+/**
+ *
+ * archive_lookup is used to determine if part of pathname refers to an regular
+ * file (archive). If so then its also used to split pathname into archive file
+ * and path used to locate file in archive. It also returns suffix of the file.
+ * How it works:
+ * We do stat of the parent of input pathname as long as we find an regular file
+ * Normally this should never happen. When routine returns true pathname modified
+ * and split into archive, inpath and suffix. Otherwise nothing happens
+ *
+ * For example:
+ *
+ * /music/path/Talco.zip/Talco - Combat Circus/12 - A la pachenka.mp3
+ * is split into archive: /music/path/Talco.zip
+ * inarchive pathname: Talco - Combat Circus/12 - A la pachenka.mp3
+ * and suffix: zip
+ */
+
+bool archive_lookup(char *pathname, char **archive, char **inpath, char **suffix)
+{
+ char *pathdupe;
+ int len, idx;
+ struct stat st_info;
+ bool ret = false;
+
+ *archive = NULL;
+ *inpath = NULL;
+ *suffix = NULL;
+
+ pathdupe = g_strdup(pathname);
+ len = idx = strlen(pathname);
+
+ while (idx > 0) {
+ //try to stat if its real directory
+ if (stat(pathdupe, &st_info) == -1) {
+ if (errno != ENOTDIR) {
+ g_warning("stat %s failed (errno=%d)\n", pathdupe, errno);
+ break;
+ }
+ } else {
+ //is something found ins original path (is not an archive)
+ if (idx == len) {
+ break;
+ }
+ //its a file ?
+ if (S_ISREG(st_info.st_mode)) {
+ //so the upper should be file
+ pathname[idx] = 0;
+ ret = true;
+ *archive = pathname;
+ *inpath = pathname + idx+1;
+
+ //try to get suffix
+ *suffix = NULL;
+ while (idx > 0) {
+ if (pathname[idx] == '.') {
+ *suffix = pathname + idx + 1;
+ break;
+ }
+ idx--;
+ }
+ break;
+ } else {
+ g_warning("not a regular file %s\n", pathdupe);
+ break;
+ }
+ }
+ //find one dir up
+ while (idx > 0) {
+ if (pathdupe[idx] == '/') {
+ pathdupe[idx] = 0;
+ break;
+ }
+ idx--;
+ }
+ }
+ g_free(pathdupe);
+ return ret;
+}
+
diff --git a/src/ArchiveLookup.hxx b/src/ArchiveLookup.hxx
new file mode 100644
index 000000000..6e7669cb0
--- /dev/null
+++ b/src/ArchiveLookup.hxx
@@ -0,0 +1,32 @@
+/*
+ * 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_ARCHIVE_LOOKUP_HXX
+#define MPD_ARCHIVE_LOOKUP_HXX
+
+/*
+ * This is the public API which is used by archive plugins to
+ * provide transparent archive decompression layer for mpd
+ *
+ */
+
+bool archive_lookup(char *pathname, char **archive, char **inpath, char **suffix);
+
+#endif
+
diff --git a/src/ArchivePlugin.cxx b/src/ArchivePlugin.cxx
new file mode 100644
index 000000000..7c5164220
--- /dev/null
+++ b/src/ArchivePlugin.cxx
@@ -0,0 +1,43 @@
+/*
+ * 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 "ArchivePlugin.hxx"
+#include "ArchiveFile.hxx"
+
+#include <assert.h>
+
+ArchiveFile *
+archive_file_open(const struct archive_plugin *plugin, const char *path,
+ GError **error_r)
+{
+ assert(plugin != NULL);
+ assert(plugin->open != NULL);
+ assert(path != NULL);
+ assert(error_r == NULL || *error_r == NULL);
+
+ ArchiveFile *file = plugin->open(path, error_r);
+
+ if (file != NULL) {
+ assert(error_r == NULL || *error_r == NULL);
+ } else {
+ assert(error_r == NULL || *error_r != NULL);
+ }
+
+ return file;
+}
diff --git a/src/ArchivePlugin.hxx b/src/ArchivePlugin.hxx
new file mode 100644
index 000000000..13952940f
--- /dev/null
+++ b/src/ArchivePlugin.hxx
@@ -0,0 +1,65 @@
+/*
+ * 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_ARCHIVE_PLUGIN_HXX
+#define MPD_ARCHIVE_PLUGIN_HXX
+
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+#include "gerror.h"
+
+struct input_stream;
+class ArchiveFile;
+class ArchiveVisitor;
+
+struct archive_plugin {
+ const char *name;
+
+ /**
+ * optional, set this to NULL if the archive plugin doesn't
+ * have/need one this must false if there is an error and
+ * true otherwise
+ */
+ bool (*init)(void);
+
+ /**
+ * optional, set this to NULL if the archive plugin doesn't
+ * have/need one
+ */
+ void (*finish)(void);
+
+ /**
+ * tryes to open archive file and associates handle with archive
+ * returns pointer to handle used is all operations with this archive
+ * or NULL when opening fails
+ */
+ ArchiveFile *(*open)(const char *path_fs, GError **error_r);
+
+ /**
+ * suffixes handled by this plugin.
+ * last element in these arrays must always be a NULL
+ */
+ const char *const*suffixes;
+};
+
+ArchiveFile *
+archive_file_open(const struct archive_plugin *plugin, const char *path,
+ GError **error_r);
+
+#endif
diff --git a/src/ArchiveVisitor.hxx b/src/ArchiveVisitor.hxx
new file mode 100644
index 000000000..e951cb5e9
--- /dev/null
+++ b/src/ArchiveVisitor.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_ARCHIVE_VISITOR_HXX
+#define MPD_ARCHIVE_VISITOR_HXX
+
+class ArchiveVisitor {
+public:
+ virtual void VisitArchiveEntry(const char *path_utf8) = 0;
+};
+
+#endif
diff --git a/src/AudioCompress/compress.h b/src/AudioCompress/compress.h
index 073d4af9a..8556d16be 100644
--- a/src/AudioCompress/compress.h
+++ b/src/AudioCompress/compress.h
@@ -19,6 +19,10 @@ struct CompressorConfig {
struct Compressor;
+#ifdef __cplusplus
+extern "C" {
+#endif
+
//! Create a new compressor (use history value of 0 for default)
struct Compressor *Compressor_new(unsigned int history);
@@ -34,7 +38,12 @@ struct CompressorConfig *Compressor_getConfig(struct Compressor *);
//! Process 16-bit signed data
void Compressor_Process_int16(struct Compressor *, int16_t *data, unsigned int count);
+#ifdef __cplusplus
+}
+#endif
+
//! TODO: Compressor_Process_int32, Compressor_Process_float, others as needed
//! TODO: functions for getting at the peak/gain/clip history buffers (for monitoring)
+
#endif
diff --git a/src/AudioConfig.cxx b/src/AudioConfig.cxx
new file mode 100644
index 000000000..9ead61afe
--- /dev/null
+++ b/src/AudioConfig.cxx
@@ -0,0 +1,51 @@
+/*
+ * 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 "AudioConfig.hxx"
+#include "AudioFormat.hxx"
+#include "AudioParser.hxx"
+#include "conf.h"
+#include "mpd_error.h"
+
+static AudioFormat configured_audio_format;
+
+AudioFormat
+getOutputAudioFormat(AudioFormat inAudioFormat)
+{
+ AudioFormat out_audio_format = inAudioFormat;
+ out_audio_format.ApplyMask(configured_audio_format);
+ return out_audio_format;
+}
+
+void initAudioConfig(void)
+{
+ const struct config_param *param = config_get_param(CONF_AUDIO_OUTPUT_FORMAT);
+ GError *error = NULL;
+ bool ret;
+
+ if (param == NULL)
+ return;
+
+ ret = audio_format_parse(configured_audio_format, param->value,
+ true, &error);
+ if (!ret)
+ MPD_ERROR("error parsing line %i: %s",
+ param->line, error->message);
+}
diff --git a/src/AudioConfig.hxx b/src/AudioConfig.hxx
new file mode 100644
index 000000000..ebe202974
--- /dev/null
+++ b/src/AudioConfig.hxx
@@ -0,0 +1,31 @@
+/*
+ * 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_AUDIO_CONFIG_HXX
+#define MPD_AUDIO_CONFIG_HXX
+
+struct AudioFormat;
+
+AudioFormat
+getOutputAudioFormat(AudioFormat inFormat);
+
+/* make sure initPlayerData is called before this function!! */
+void initAudioConfig(void);
+
+#endif
diff --git a/src/AudioFormat.cxx b/src/AudioFormat.cxx
new file mode 100644
index 000000000..04636c1e2
--- /dev/null
+++ b/src/AudioFormat.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 "AudioFormat.hxx"
+
+#include <assert.h>
+#include <stdio.h>
+
+void
+AudioFormat::ApplyMask(AudioFormat mask)
+{
+ assert(IsValid());
+ assert(mask.IsMaskValid());
+
+ if (mask.sample_rate != 0)
+ sample_rate = mask.sample_rate;
+
+ if (mask.format != SampleFormat::UNDEFINED)
+ format = mask.format;
+
+ if (mask.channels != 0)
+ channels = mask.channels;
+
+ assert(IsValid());
+}
+
+const char *
+sample_format_to_string(SampleFormat format)
+{
+ switch (format) {
+ case SampleFormat::UNDEFINED:
+ return "?";
+
+ case SampleFormat::S8:
+ return "8";
+
+ case SampleFormat::S16:
+ return "16";
+
+ case SampleFormat::S24_P32:
+ return "24";
+
+ case SampleFormat::S32:
+ return "32";
+
+ case SampleFormat::FLOAT:
+ return "f";
+
+ case SampleFormat::DSD:
+ return "dsd";
+ }
+
+ /* unreachable */
+ assert(false);
+ gcc_unreachable();
+}
+
+const char *
+audio_format_to_string(const AudioFormat af,
+ struct audio_format_string *s)
+{
+ assert(s != nullptr);
+
+ snprintf(s->buffer, sizeof(s->buffer), "%u:%s:%u",
+ af.sample_rate, sample_format_to_string(af.format),
+ af.channels);
+
+ return s->buffer;
+}
diff --git a/src/AudioFormat.hxx b/src/AudioFormat.hxx
new file mode 100644
index 000000000..eb3f9b062
--- /dev/null
+++ b/src/AudioFormat.hxx
@@ -0,0 +1,315 @@
+/*
+ * 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_AUDIO_FORMAT_HXX
+#define MPD_AUDIO_FORMAT_HXX
+
+#include "gcc.h"
+
+#include <stdint.h>
+#include <assert.h>
+
+#if defined(WIN32) && GCC_CHECK_VERSION(4,6)
+/* on WIN32, "FLOAT" is already defined, and this triggers -Wshadow */
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wshadow"
+#endif
+
+enum class SampleFormat : uint8_t {
+ UNDEFINED = 0,
+
+ S8,
+ S16,
+
+ /**
+ * Signed 24 bit integer samples, packed in 32 bit integers
+ * (the most significant byte is filled with the sign bit).
+ */
+ S24_P32,
+
+ S32,
+
+ /**
+ * 32 bit floating point samples in the host's format. The
+ * range is -1.0f to +1.0f.
+ */
+ FLOAT,
+
+ /**
+ * Direct Stream Digital. 1-bit samples; each frame has one
+ * byte (8 samples) per channel.
+ */
+ DSD,
+};
+
+#if defined(WIN32) && GCC_CHECK_VERSION(4,6)
+#pragma GCC diagnostic pop
+#endif
+
+static constexpr unsigned MAX_CHANNELS = 8;
+
+/**
+ * This structure describes the format of a raw PCM stream.
+ */
+struct AudioFormat {
+ /**
+ * The sample rate in Hz. A better name for this attribute is
+ * "frame rate", because technically, you have two samples per
+ * frame in stereo sound.
+ */
+ uint32_t sample_rate;
+
+ /**
+ * The format samples are stored in. See the #sample_format
+ * enum for valid values.
+ */
+ SampleFormat format;
+
+ /**
+ * The number of channels. Only mono (1) and stereo (2) are
+ * fully supported currently.
+ */
+ uint8_t channels;
+
+ AudioFormat() = default;
+
+ constexpr AudioFormat(uint32_t _sample_rate,
+ SampleFormat _format, uint8_t _channels)
+ :sample_rate(_sample_rate),
+ format(_format), channels(_channels) {}
+
+ static constexpr AudioFormat Undefined() {
+ return AudioFormat(0, SampleFormat::UNDEFINED,0);
+ }
+
+ /**
+ * Clears the #audio_format object, i.e. sets all attributes to an
+ * undefined (invalid) value.
+ */
+ void Clear() {
+ sample_rate = 0;
+ format = SampleFormat::UNDEFINED;
+ channels = 0;
+ }
+
+ /**
+ * Checks whether the object has a defined value.
+ */
+ constexpr bool IsDefined() const {
+ return sample_rate != 0;
+ }
+
+ /**
+ * Checks whether the object is full, i.e. all attributes are
+ * defined. This is more complete than IsDefined(), but
+ * slower.
+ */
+ constexpr bool IsFullyDefined() const {
+ return sample_rate != 0 && format != SampleFormat::UNDEFINED &&
+ channels != 0;
+ }
+
+ /**
+ * Checks whether the object has at least one defined value.
+ */
+ constexpr bool IsMaskDefined() const {
+ return sample_rate != 0 || format != SampleFormat::UNDEFINED ||
+ channels != 0;
+ }
+
+ bool IsValid() const;
+ bool IsMaskValid() const;
+
+ constexpr bool operator==(const AudioFormat other) const {
+ return sample_rate == other.sample_rate &&
+ format == other.format &&
+ channels == other.channels;
+ }
+
+ constexpr bool operator!=(const AudioFormat other) const {
+ return !(*this == other);
+ }
+
+ void ApplyMask(AudioFormat mask);
+
+ /**
+ * Returns the size of each (mono) sample in bytes.
+ */
+ unsigned GetSampleSize() const;
+
+ /**
+ * Returns the size of each full frame in bytes.
+ */
+ unsigned GetFrameSize() const;
+
+ /**
+ * Returns the floating point factor which converts a time
+ * span to a storage size in bytes.
+ */
+ double GetTimeToSize() const;
+};
+
+/**
+ * Buffer for audio_format_string().
+ */
+struct audio_format_string {
+ char buffer[24];
+};
+
+/**
+ * Checks whether the sample rate is valid.
+ *
+ * @param sample_rate the sample rate in Hz
+ */
+static constexpr inline bool
+audio_valid_sample_rate(unsigned sample_rate)
+{
+ return sample_rate > 0 && sample_rate < (1 << 30);
+}
+
+/**
+ * Checks whether the sample format is valid.
+ *
+ * @param bits the number of significant bits per sample
+ */
+static inline bool
+audio_valid_sample_format(SampleFormat format)
+{
+ switch (format) {
+ case SampleFormat::S8:
+ case SampleFormat::S16:
+ case SampleFormat::S24_P32:
+ case SampleFormat::S32:
+ case SampleFormat::FLOAT:
+ case SampleFormat::DSD:
+ return true;
+
+ case SampleFormat::UNDEFINED:
+ break;
+ }
+
+ return false;
+}
+
+/**
+ * Checks whether the number of channels is valid.
+ */
+static constexpr inline bool
+audio_valid_channel_count(unsigned channels)
+{
+ return channels >= 1 && channels <= MAX_CHANNELS;
+}
+
+/**
+ * Returns false if the format is not valid for playback with MPD.
+ * This function performs some basic validity checks.
+ */
+inline bool
+AudioFormat::IsValid() const
+{
+ return audio_valid_sample_rate(sample_rate) &&
+ audio_valid_sample_format(format) &&
+ audio_valid_channel_count(channels);
+}
+
+/**
+ * Returns false if the format mask is not valid for playback with
+ * MPD. This function performs some basic validity checks.
+ */
+inline bool
+AudioFormat::IsMaskValid() const
+{
+ return (sample_rate == 0 ||
+ audio_valid_sample_rate(sample_rate)) &&
+ (format == SampleFormat::UNDEFINED ||
+ audio_valid_sample_format(format)) &&
+ (channels == 0 || audio_valid_channel_count(channels));
+}
+
+gcc_const
+static inline unsigned
+sample_format_size(SampleFormat format)
+{
+ switch (format) {
+ case SampleFormat::S8:
+ return 1;
+
+ case SampleFormat::S16:
+ return 2;
+
+ case SampleFormat::S24_P32:
+ case SampleFormat::S32:
+ case SampleFormat::FLOAT:
+ return 4;
+
+ case SampleFormat::DSD:
+ /* each frame has 8 samples per channel */
+ return 1;
+
+ case SampleFormat::UNDEFINED:
+ return 0;
+ }
+
+ assert(false);
+ gcc_unreachable();
+}
+
+inline unsigned
+AudioFormat::GetSampleSize() const
+{
+ return sample_format_size(format);
+}
+
+inline unsigned
+AudioFormat::GetFrameSize() const
+{
+ return GetSampleSize() * channels;
+}
+
+inline double
+AudioFormat::GetTimeToSize() const
+{
+ return sample_rate * GetFrameSize();
+}
+
+/**
+ * Renders a #sample_format enum into a string, e.g. for printing it
+ * in a log file.
+ *
+ * @param format a #sample_format enum value
+ * @return the string
+ */
+gcc_pure gcc_malloc
+const char *
+sample_format_to_string(SampleFormat format);
+
+/**
+ * Renders the #audio_format object into a string, e.g. for printing
+ * it in a log file.
+ *
+ * @param af the #audio_format object
+ * @param s a buffer to print into
+ * @return the string, or NULL if the #audio_format object is invalid
+ */
+gcc_pure gcc_malloc
+const char *
+audio_format_to_string(AudioFormat af,
+ struct audio_format_string *s);
+
+#endif
diff --git a/src/AudioParser.cxx b/src/AudioParser.cxx
new file mode 100644
index 000000000..4c345ca33
--- /dev/null
+++ b/src/AudioParser.cxx
@@ -0,0 +1,223 @@
+/*
+ * 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.
+ */
+
+/*
+ * Parser functions for audio related objects.
+ *
+ */
+
+#include "config.h"
+#include "AudioParser.hxx"
+#include "AudioFormat.hxx"
+#include "CheckAudioFormat.hxx"
+#include "gcc.h"
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+
+/**
+ * The GLib quark used for errors reported by this library.
+ */
+static inline GQuark
+audio_parser_quark(void)
+{
+ return g_quark_from_static_string("audio_parser");
+}
+
+static bool
+parse_sample_rate(const char *src, bool mask, uint32_t *sample_rate_r,
+ const char **endptr_r, GError **error_r)
+{
+ unsigned long value;
+ char *endptr;
+
+ if (mask && *src == '*') {
+ *sample_rate_r = 0;
+ *endptr_r = src + 1;
+ return true;
+ }
+
+ value = strtoul(src, &endptr, 10);
+ if (endptr == src) {
+ g_set_error(error_r, audio_parser_quark(), 0,
+ "Failed to parse the sample rate");
+ return false;
+ } else if (!audio_check_sample_rate(value, error_r))
+ return false;
+
+ *sample_rate_r = value;
+ *endptr_r = endptr;
+ return true;
+}
+
+static bool
+parse_sample_format(const char *src, bool mask,
+ SampleFormat *sample_format_r,
+ const char **endptr_r, GError **error_r)
+{
+ unsigned long value;
+ char *endptr;
+ SampleFormat sample_format;
+
+ if (mask && *src == '*') {
+ *sample_format_r = SampleFormat::UNDEFINED;
+ *endptr_r = src + 1;
+ return true;
+ }
+
+ if (*src == 'f') {
+ *sample_format_r = SampleFormat::FLOAT;
+ *endptr_r = src + 1;
+ return true;
+ }
+
+ if (memcmp(src, "dsd", 3) == 0) {
+ *sample_format_r = SampleFormat::DSD;
+ *endptr_r = src + 3;
+ return true;
+ }
+
+ value = strtoul(src, &endptr, 10);
+ if (endptr == src) {
+ g_set_error(error_r, audio_parser_quark(), 0,
+ "Failed to parse the sample format");
+ return false;
+ }
+
+ switch (value) {
+ case 8:
+ sample_format = SampleFormat::S8;
+ break;
+
+ case 16:
+ sample_format = SampleFormat::S16;
+ break;
+
+ case 24:
+ if (memcmp(endptr, "_3", 2) == 0)
+ /* for backwards compatibility */
+ endptr += 2;
+
+ sample_format = SampleFormat::S24_P32;
+ break;
+
+ case 32:
+ sample_format = SampleFormat::S32;
+ break;
+
+ default:
+ g_set_error(error_r, audio_parser_quark(), 0,
+ "Invalid sample format: %lu", value);
+ return false;
+ }
+
+ assert(audio_valid_sample_format(sample_format));
+
+ *sample_format_r = sample_format;
+ *endptr_r = endptr;
+ return true;
+}
+
+static bool
+parse_channel_count(const char *src, bool mask, uint8_t *channels_r,
+ const char **endptr_r, GError **error_r)
+{
+ unsigned long value;
+ char *endptr;
+
+ if (mask && *src == '*') {
+ *channels_r = 0;
+ *endptr_r = src + 1;
+ return true;
+ }
+
+ value = strtoul(src, &endptr, 10);
+ if (endptr == src) {
+ g_set_error(error_r, audio_parser_quark(), 0,
+ "Failed to parse the channel count");
+ return false;
+ } else if (!audio_check_channel_count(value, error_r))
+ return false;
+
+ *channels_r = value;
+ *endptr_r = endptr;
+ return true;
+}
+
+bool
+audio_format_parse(AudioFormat &dest, const char *src,
+ bool mask, GError **error_r)
+{
+ uint32_t rate;
+ SampleFormat sample_format;
+ uint8_t channels;
+
+ dest.Clear();
+
+ /* parse sample rate */
+
+#if GCC_CHECK_VERSION(4,7)
+ /* workaround -Wmaybe-uninitialized false positive */
+ rate = 0;
+#endif
+
+ if (!parse_sample_rate(src, mask, &rate, &src, error_r))
+ return false;
+
+ if (*src++ != ':') {
+ g_set_error(error_r, audio_parser_quark(), 0,
+ "Sample format missing");
+ return false;
+ }
+
+ /* parse sample format */
+
+#if GCC_CHECK_VERSION(4,7)
+ /* workaround -Wmaybe-uninitialized false positive */
+ sample_format = SampleFormat::UNDEFINED;
+#endif
+
+ if (!parse_sample_format(src, mask, &sample_format, &src, error_r))
+ return false;
+
+ if (*src++ != ':') {
+ g_set_error(error_r, audio_parser_quark(), 0,
+ "Channel count missing");
+ return false;
+ }
+
+ /* parse channel count */
+
+ if (!parse_channel_count(src, mask, &channels, &src, error_r))
+ return false;
+
+ if (*src != 0) {
+ g_set_error(error_r, audio_parser_quark(), 0,
+ "Extra data after channel count: %s", src);
+ return false;
+ }
+
+ dest = AudioFormat(rate, sample_format, channels);
+ assert(mask
+ ? dest.IsMaskValid()
+ : dest.IsValid());
+
+ return true;
+}
diff --git a/src/AudioParser.hxx b/src/AudioParser.hxx
new file mode 100644
index 000000000..14c26392c
--- /dev/null
+++ b/src/AudioParser.hxx
@@ -0,0 +1,47 @@
+/*
+ * 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
+ *
+ * Parser functions for audio related objects.
+ */
+
+#ifndef MPD_AUDIO_PARSER_HXX
+#define MPD_AUDIO_PARSER_HXX
+
+#include "gerror.h"
+
+struct AudioFormat;
+
+/**
+ * Parses a string in the form "SAMPLE_RATE:BITS:CHANNELS" into an
+ * #audio_format.
+ *
+ * @param dest the destination #audio_format struct
+ * @param src the input string
+ * @param mask if true, then "*" is allowed for any number of items
+ * @param error_r location to store the error occurring, or NULL to
+ * ignore errors
+ * @return true on success
+ */
+bool
+audio_format_parse(AudioFormat &dest, const char *src,
+ bool mask, GError **error_r);
+
+#endif
diff --git a/src/CheckAudioFormat.cxx b/src/CheckAudioFormat.cxx
new file mode 100644
index 000000000..fabffd56c
--- /dev/null
+++ b/src/CheckAudioFormat.cxx
@@ -0,0 +1,75 @@
+/*
+ * 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 "CheckAudioFormat.hxx"
+#include "AudioFormat.hxx"
+
+#include <assert.h>
+
+bool
+audio_check_sample_rate(unsigned long sample_rate, GError **error_r)
+{
+ if (!audio_valid_sample_rate(sample_rate)) {
+ g_set_error(error_r, audio_format_quark(), 0,
+ "Invalid sample rate: %lu", sample_rate);
+ return false;
+ }
+
+ return true;
+}
+
+bool
+audio_check_sample_format(SampleFormat sample_format, GError **error_r)
+{
+ if (!audio_valid_sample_format(sample_format)) {
+ g_set_error(error_r, audio_format_quark(), 0,
+ "Invalid sample format: %u",
+ unsigned(sample_format));
+ return false;
+ }
+
+ return true;
+}
+
+bool
+audio_check_channel_count(unsigned channels, GError **error_r)
+{
+ if (!audio_valid_channel_count(channels)) {
+ g_set_error(error_r, audio_format_quark(), 0,
+ "Invalid channel count: %u", channels);
+ return false;
+ }
+
+ return true;
+}
+
+bool
+audio_format_init_checked(AudioFormat &af, unsigned long sample_rate,
+ SampleFormat sample_format, unsigned channels,
+ GError **error_r)
+{
+ if (audio_check_sample_rate(sample_rate, error_r) &&
+ audio_check_sample_format(sample_format, error_r) &&
+ audio_check_channel_count(channels, error_r)) {
+ af = AudioFormat(sample_rate, sample_format, channels);
+ assert(af.IsValid());
+ return true;
+ } else
+ return false;
+}
diff --git a/src/CheckAudioFormat.hxx b/src/CheckAudioFormat.hxx
new file mode 100644
index 000000000..7fbce7f98
--- /dev/null
+++ b/src/CheckAudioFormat.hxx
@@ -0,0 +1,54 @@
+/*
+ * 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_CHECK_AUDIO_FORMAT_HXX
+#define MPD_CHECK_AUDIO_FORMAT_HXX
+
+#include "AudioFormat.hxx"
+
+#include <glib.h>
+
+/**
+ * The GLib quark used for errors reported by this library.
+ */
+gcc_const
+static inline GQuark
+audio_format_quark(void)
+{
+ return g_quark_from_static_string("audio_format");
+}
+
+bool
+audio_check_sample_rate(unsigned long sample_rate, GError **error_r);
+
+bool
+audio_check_sample_format(SampleFormat sample_format, GError **error_r);
+
+bool
+audio_check_channel_count(unsigned sample_format, GError **error_r);
+
+/**
+ * Wrapper for audio_format_init(), which checks all attributes.
+ */
+bool
+audio_format_init_checked(AudioFormat &af, unsigned long sample_rate,
+ SampleFormat sample_format, unsigned channels,
+ GError **error_r);
+
+#endif
diff --git a/src/Client.cxx b/src/Client.cxx
new file mode 100644
index 000000000..be121dfe8
--- /dev/null
+++ b/src/Client.cxx
@@ -0,0 +1,36 @@
+/*
+ * 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 "ClientInternal.hxx"
+
+int client_get_uid(const Client *client)
+{
+ return client->uid;
+}
+
+unsigned client_get_permission(const Client *client)
+{
+ return client->permission;
+}
+
+void client_set_permission(Client *client, unsigned permission)
+{
+ client->permission = permission;
+}
diff --git a/src/Client.hxx b/src/Client.hxx
new file mode 100644
index 000000000..1456f1b7d
--- /dev/null
+++ b/src/Client.hxx
@@ -0,0 +1,79 @@
+/*
+ * 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_CLIENT_H
+#define MPD_CLIENT_H
+
+#include "gcc.h"
+
+#include <stddef.h>
+#include <stdarg.h>
+
+struct sockaddr;
+class EventLoop;
+struct Partition;
+class Client;
+
+void client_manager_init(void);
+
+void
+client_new(EventLoop &loop, Partition &partition,
+ int fd, const struct sockaddr *sa, size_t sa_length, int uid);
+
+/**
+ * returns the uid of the client process, or a negative value if the
+ * uid is unknown
+ */
+gcc_pure
+int client_get_uid(const Client *client);
+
+/**
+ * Is this client running on the same machine, connected with a local
+ * (UNIX domain) socket?
+ */
+gcc_pure
+static inline bool
+client_is_local(const Client *client)
+{
+ return client_get_uid(client) > 0;
+}
+
+gcc_pure
+unsigned client_get_permission(const Client *client);
+
+void client_set_permission(Client *client, unsigned permission);
+
+/**
+ * Write a C string to the client.
+ */
+void client_puts(Client *client, const char *s);
+
+/**
+ * Write a printf-like formatted string to the client.
+ */
+void client_vprintf(Client *client, const char *fmt, va_list args);
+
+/**
+ * Write a printf-like formatted string to the client.
+ */
+gcc_fprintf
+void
+client_printf(Client *client, const char *fmt, ...);
+
+#endif
diff --git a/src/ClientEvent.cxx b/src/ClientEvent.cxx
new file mode 100644
index 000000000..905cf0c0a
--- /dev/null
+++ b/src/ClientEvent.cxx
@@ -0,0 +1,36 @@
+/*
+ * 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 "ClientInternal.hxx"
+
+void
+Client::OnSocketError(GError *error)
+{
+ g_warning("error on client %d: %s", num, error->message);
+ g_error_free(error);
+
+ SetExpired();
+}
+
+void
+Client::OnSocketClosed()
+{
+ SetExpired();
+}
diff --git a/src/ClientExpire.cxx b/src/ClientExpire.cxx
new file mode 100644
index 000000000..e5f9a9867
--- /dev/null
+++ b/src/ClientExpire.cxx
@@ -0,0 +1,42 @@
+/*
+ * 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 "ClientInternal.hxx"
+
+void
+Client::SetExpired()
+{
+ if (IsExpired())
+ return;
+
+ FullyBufferedSocket::Close();
+ TimeoutMonitor::Schedule(0);
+}
+
+void
+Client::OnTimeout()
+{
+ if (!IsExpired()) {
+ assert(!idle_waiting);
+ g_debug("[%u] timeout", num);
+ }
+
+ Close();
+}
diff --git a/src/ClientFile.cxx b/src/ClientFile.cxx
new file mode 100644
index 000000000..07fa767b0
--- /dev/null
+++ b/src/ClientFile.cxx
@@ -0,0 +1,73 @@
+/*
+ * 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 "ClientFile.hxx"
+#include "Client.hxx"
+#include "ack.h"
+#include "io_error.h"
+#include "fs/Path.hxx"
+#include "fs/FileSystem.hxx"
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <unistd.h>
+
+bool
+client_allow_file(const Client *client, const Path &path_fs,
+ GError **error_r)
+{
+#ifdef WIN32
+ (void)client;
+ (void)path_fs;
+
+ g_set_error(error_r, ack_quark(), ACK_ERROR_PERMISSION,
+ "Access denied");
+ return false;
+#else
+ const int uid = client_get_uid(client);
+ if (uid >= 0 && (uid_t)uid == geteuid())
+ /* always allow access if user runs his own MPD
+ instance */
+ return true;
+
+ if (uid <= 0) {
+ /* unauthenticated client */
+ g_set_error(error_r, ack_quark(), ACK_ERROR_PERMISSION,
+ "Access denied");
+ return false;
+ }
+
+ struct stat st;
+ if (!StatFile(path_fs, st)) {
+ set_error_errno(error_r);
+ return false;
+ }
+
+ if (st.st_uid != (uid_t)uid && (st.st_mode & 0444) != 0444) {
+ /* client is not owner */
+ g_set_error(error_r, ack_quark(), ACK_ERROR_PERMISSION,
+ "Access denied");
+ return false;
+ }
+
+ return true;
+#endif
+}
diff --git a/src/ClientFile.hxx b/src/ClientFile.hxx
new file mode 100644
index 000000000..21db4d3ad
--- /dev/null
+++ b/src/ClientFile.hxx
@@ -0,0 +1,42 @@
+/*
+ * 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_CLIENT_FILE_HXX
+#define MPD_CLIENT_FILE_HXX
+
+#include "gerror.h"
+
+class Client;
+class Path;
+
+/**
+ * Is this client allowed to use the specified local file?
+ *
+ * Note that this function is vulnerable to timing/symlink attacks.
+ * We cannot fix this as long as there are plugins that open a file by
+ * its name, and not by file descriptor / callbacks.
+ *
+ * @param path_fs the absolute path name in filesystem encoding
+ * @return true if access is allowed
+ */
+bool
+client_allow_file(const Client *client, const Path &path_fs,
+ GError **error_r);
+
+#endif
diff --git a/src/ClientGlobal.cxx b/src/ClientGlobal.cxx
new file mode 100644
index 000000000..6115a7856
--- /dev/null
+++ b/src/ClientGlobal.cxx
@@ -0,0 +1,48 @@
+/*
+ * 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 "ClientInternal.hxx"
+#include "ClientList.hxx"
+#include "conf.h"
+
+#include <assert.h>
+
+#define CLIENT_TIMEOUT_DEFAULT (60)
+#define CLIENT_MAX_COMMAND_LIST_DEFAULT (2048*1024)
+#define CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT (8192*1024)
+
+int client_timeout;
+size_t client_max_command_list_size;
+size_t client_max_output_buffer_size;
+
+void client_manager_init(void)
+{
+ client_timeout = config_get_positive(CONF_CONN_TIMEOUT,
+ CLIENT_TIMEOUT_DEFAULT);
+ client_max_command_list_size =
+ config_get_positive(CONF_MAX_COMMAND_LIST_SIZE,
+ CLIENT_MAX_COMMAND_LIST_DEFAULT / 1024)
+ * 1024;
+
+ client_max_output_buffer_size =
+ config_get_positive(CONF_MAX_OUTPUT_BUFFER_SIZE,
+ CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT / 1024)
+ * 1024;
+}
diff --git a/src/ClientIdle.cxx b/src/ClientIdle.cxx
new file mode 100644
index 000000000..714438123
--- /dev/null
+++ b/src/ClientIdle.cxx
@@ -0,0 +1,75 @@
+/*
+ * 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 "ClientInternal.hxx"
+#include "Idle.hxx"
+
+#include <assert.h>
+
+void
+Client::IdleNotify()
+{
+ assert(idle_waiting);
+ assert(idle_flags != 0);
+
+ unsigned flags = idle_flags;
+ idle_flags = 0;
+ idle_waiting = false;
+
+ const char *const*idle_names = idle_get_names();
+ for (unsigned i = 0; idle_names[i]; ++i) {
+ if (flags & (1 << i) & idle_subscriptions)
+ client_printf(this, "changed: %s\n",
+ idle_names[i]);
+ }
+
+ client_puts(this, "OK\n");
+
+ TimeoutMonitor::ScheduleSeconds(client_timeout);
+}
+
+void
+Client::IdleAdd(unsigned flags)
+{
+ if (IsExpired())
+ return;
+
+ idle_flags |= flags;
+ if (idle_waiting && (idle_flags & idle_subscriptions))
+ IdleNotify();
+}
+
+bool
+Client::IdleWait(unsigned flags)
+{
+ assert(!idle_waiting);
+
+ idle_waiting = true;
+ idle_subscriptions = flags;
+
+ if (idle_flags & idle_subscriptions) {
+ IdleNotify();
+ return true;
+ } else {
+ /* disable timeouts while in "idle" */
+ TimeoutMonitor::Cancel();
+ return false;
+ }
+}
diff --git a/src/ClientInternal.hxx b/src/ClientInternal.hxx
new file mode 100644
index 000000000..8dd839cc2
--- /dev/null
+++ b/src/ClientInternal.hxx
@@ -0,0 +1,133 @@
+/*
+ * 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_CLIENT_INTERNAL_HXX
+#define MPD_CLIENT_INTERNAL_HXX
+
+#include "check.h"
+#include "Client.hxx"
+#include "ClientMessage.hxx"
+#include "CommandListBuilder.hxx"
+#include "event/FullyBufferedSocket.hxx"
+#include "event/TimeoutMonitor.hxx"
+#include "command.h"
+
+#include <set>
+#include <string>
+#include <list>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "client"
+
+enum {
+ CLIENT_MAX_SUBSCRIPTIONS = 16,
+ CLIENT_MAX_MESSAGES = 64,
+};
+
+struct Partition;
+
+class Client final : private FullyBufferedSocket, TimeoutMonitor {
+public:
+ Partition &partition;
+ struct playlist &playlist;
+ struct player_control *player_control;
+
+ unsigned permission;
+
+ /** the uid of the client process, or -1 if unknown */
+ int uid;
+
+ CommandListBuilder cmd_list;
+
+ unsigned int num; /* client number */
+
+ /** is this client waiting for an "idle" response? */
+ bool idle_waiting;
+
+ /** idle flags pending on this client, to be sent as soon as
+ the client enters "idle" */
+ unsigned idle_flags;
+
+ /** idle flags that the client wants to receive */
+ unsigned idle_subscriptions;
+
+ /**
+ * A list of channel names this client is subscribed to.
+ */
+ std::set<std::string> subscriptions;
+
+ /**
+ * The number of subscriptions in #subscriptions. Used to
+ * limit the number of subscriptions.
+ */
+ unsigned num_subscriptions;
+
+ /**
+ * A list of messages this client has received.
+ */
+ std::list<ClientMessage> messages;
+
+ Client(EventLoop &loop, Partition &partition,
+ int fd, int uid, int num);
+
+ bool IsConnected() const {
+ return FullyBufferedSocket::IsDefined();
+ }
+
+ gcc_pure
+ bool IsSubscribed(const char *channel_name) const {
+ return subscriptions.find(channel_name) != subscriptions.end();
+ }
+
+ gcc_pure
+ bool IsExpired() const {
+ return !FullyBufferedSocket::IsDefined();
+ }
+
+ void Close();
+ void SetExpired();
+
+ using FullyBufferedSocket::Write;
+
+ /**
+ * Send "idle" response to this client.
+ */
+ void IdleNotify();
+ void IdleAdd(unsigned flags);
+ bool IdleWait(unsigned flags);
+
+private:
+ /* virtual methods from class BufferedSocket */
+ virtual InputResult OnSocketInput(const void *data,
+ size_t length) override;
+ virtual void OnSocketError(GError *error) override;
+ virtual void OnSocketClosed() override;
+
+ /* virtual methods from class TimeoutMonitor */
+ virtual void OnTimeout() override;
+};
+
+extern int client_timeout;
+extern size_t client_max_command_list_size;
+extern size_t client_max_output_buffer_size;
+
+enum command_return
+client_process_line(Client *client, char *line);
+
+#endif
diff --git a/src/ClientList.cxx b/src/ClientList.cxx
new file mode 100644
index 000000000..37e6f1289
--- /dev/null
+++ b/src/ClientList.cxx
@@ -0,0 +1,56 @@
+/*
+ * 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 "ClientList.hxx"
+#include "ClientInternal.hxx"
+
+#include <algorithm>
+
+#include <assert.h>
+
+void
+ClientList::Remove(Client &client)
+{
+ assert(size > 0);
+ assert(!list.empty());
+
+ auto i = std::find(list.begin(), list.end(), &client);
+ assert(i != list.end());
+ list.erase(i);
+ --size;
+}
+
+void
+ClientList::CloseAll()
+{
+ while (!list.empty())
+ list.front()->Close();
+
+ assert(size == 0);
+}
+
+void
+ClientList::IdleAdd(unsigned flags)
+{
+ assert(flags != 0);
+
+ for (const auto &client : list)
+ client->IdleAdd(flags);
+}
diff --git a/src/ClientList.hxx b/src/ClientList.hxx
new file mode 100644
index 000000000..e8560af78
--- /dev/null
+++ b/src/ClientList.hxx
@@ -0,0 +1,61 @@
+/*
+ * 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_CLIENT_LIST_HXX
+#define MPD_CLIENT_LIST_HXX
+
+#include <list>
+
+class Client;
+
+class ClientList {
+ const unsigned max_size;
+
+ unsigned size;
+ std::list<Client *> list;
+
+public:
+ ClientList(unsigned _max_size)
+ :max_size(_max_size), size(0) {}
+
+ std::list<Client *>::iterator begin() {
+ return list.begin();
+ }
+
+ std::list<Client *>::iterator end() {
+ return list.end();
+ }
+
+ bool IsFull() const {
+ return size >= max_size;
+ }
+
+ void Add(Client &client) {
+ list.push_front(&client);
+ ++size;
+ }
+
+ void Remove(Client &client);
+
+ void CloseAll();
+
+ void IdleAdd(unsigned flags);
+};
+
+#endif
diff --git a/src/ClientMessage.cxx b/src/ClientMessage.cxx
new file mode 100644
index 000000000..619964b3c
--- /dev/null
+++ b/src/ClientMessage.cxx
@@ -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.
+ */
+
+#include "ClientMessage.hxx"
+
+#include <glib.h>
+
+G_GNUC_PURE
+static bool
+valid_channel_char(const char ch)
+{
+ return g_ascii_isalnum(ch) ||
+ ch == '_' || ch == '-' || ch == '.' || ch == ':';
+}
+
+bool
+client_message_valid_channel_name(const char *name)
+{
+ do {
+ if (!valid_channel_char(*name))
+ return false;
+ } while (*++name != 0);
+
+ return true;
+}
diff --git a/src/ClientMessage.hxx b/src/ClientMessage.hxx
new file mode 100644
index 000000000..2a929d445
--- /dev/null
+++ b/src/ClientMessage.hxx
@@ -0,0 +1,52 @@
+/*
+ * 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_CLIENT_MESSAGE_H
+#define MPD_CLIENT_MESSAGE_H
+
+#include "gcc.h"
+
+#include <string>
+
+/**
+ * A client-to-client message.
+ */
+class ClientMessage {
+ std::string channel, message;
+
+public:
+ template<typename T, typename U>
+ ClientMessage(T &&_channel, U &&_message)
+ :channel(std::forward<T>(_channel)),
+ message(std::forward<U>(_message)) {}
+
+ const char *GetChannel() const {
+ return channel.c_str();
+ }
+
+ const char *GetMessage() const {
+ return message.c_str();
+ }
+};
+
+gcc_pure
+bool
+client_message_valid_channel_name(const char *name);
+
+#endif
diff --git a/src/ClientNew.cxx b/src/ClientNew.cxx
new file mode 100644
index 000000000..22127c491
--- /dev/null
+++ b/src/ClientNew.cxx
@@ -0,0 +1,127 @@
+/*
+ * 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 "ClientInternal.hxx"
+#include "ClientList.hxx"
+#include "Partition.hxx"
+#include "Instance.hxx"
+#include "fd_util.h"
+extern "C" {
+#include "resolver.h"
+}
+#include "Permission.hxx"
+
+#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>
+#endif
+
+
+#define LOG_LEVEL_SECURE G_LOG_LEVEL_INFO
+
+static const char GREETING[] = "OK MPD " PROTOCOL_VERSION "\n";
+
+Client::Client(EventLoop &_loop, Partition &_partition,
+ int _fd, int _uid, int _num)
+ :FullyBufferedSocket(_fd, _loop, 16384, client_max_output_buffer_size),
+ TimeoutMonitor(_loop),
+ partition(_partition),
+ playlist(partition.playlist), player_control(&partition.pc),
+ permission(getDefaultPermissions()),
+ uid(_uid),
+ num(_num),
+ idle_waiting(false), idle_flags(0),
+ num_subscriptions(0)
+{
+ TimeoutMonitor::ScheduleSeconds(client_timeout);
+}
+
+void
+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;
+
+ assert(fd >= 0);
+
+#ifdef HAVE_LIBWRAP
+ if (sa->sa_family != AF_UNIX) {
+ char *hostaddr = sockaddr_to_string(sa, sa_length, NULL);
+ const char *progname = g_get_prgname();
+
+ struct request_info req;
+ request_init(&req, RQ_FILE, fd, RQ_DAEMON, progname, 0);
+
+ fromhost(&req);
+
+ if (!hosts_access(&req)) {
+ /* tcp wrappers says no */
+ g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE,
+ "libwrap refused connection (libwrap=%s) from %s",
+ progname, hostaddr);
+
+ g_free(hostaddr);
+ close_socket(fd);
+ return;
+ }
+
+ g_free(hostaddr);
+ }
+#endif /* HAVE_WRAP */
+
+ ClientList &client_list = *partition.instance.client_list;
+ if (client_list.IsFull()) {
+ g_warning("Max Connections Reached!");
+ close_socket(fd);
+ return;
+ }
+
+ Client *client = new Client(loop, partition, fd, uid,
+ next_client_num++);
+
+ (void)send(fd, GREETING, sizeof(GREETING) - 1, 0);
+
+ client_list.Add(*client);
+
+ remote = sockaddr_to_string(sa, sa_length, NULL);
+ g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE,
+ "[%u] opened from %s", client->num, remote);
+ g_free(remote);
+}
+
+void
+Client::Close()
+{
+ partition.instance.client_list->Remove(*this);
+
+ SetExpired();
+
+ g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE, "[%u] closed", num);
+ delete this;
+}
diff --git a/src/ClientProcess.cxx b/src/ClientProcess.cxx
new file mode 100644
index 000000000..bcd20d1b7
--- /dev/null
+++ b/src/ClientProcess.cxx
@@ -0,0 +1,135 @@
+/*
+ * 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 "ClientInternal.hxx"
+#include "protocol/Result.hxx"
+#include "AllCommands.hxx"
+
+#include <string.h>
+
+#define CLIENT_LIST_MODE_BEGIN "command_list_begin"
+#define CLIENT_LIST_OK_MODE_BEGIN "command_list_ok_begin"
+#define CLIENT_LIST_MODE_END "command_list_end"
+
+static enum command_return
+client_process_command_list(Client *client, bool list_ok,
+ std::list<std::string> &&list)
+{
+ enum command_return ret = COMMAND_RETURN_OK;
+ unsigned num = 0;
+
+ for (auto &&i : list) {
+ char *cmd = &*i.begin();
+
+ g_debug("command_process_list: process command \"%s\"",
+ cmd);
+ ret = command_process(client, num++, cmd);
+ g_debug("command_process_list: command returned %i", ret);
+ if (ret != COMMAND_RETURN_OK || client->IsExpired())
+ break;
+ else if (list_ok)
+ client_puts(client, "list_OK\n");
+ }
+
+ return ret;
+}
+
+enum command_return
+client_process_line(Client *client, char *line)
+{
+ enum command_return ret;
+
+ if (strcmp(line, "noidle") == 0) {
+ if (client->idle_waiting) {
+ /* send empty idle response and leave idle mode */
+ client->idle_waiting = false;
+ command_success(client);
+ }
+
+ /* do nothing if the client wasn't idling: the client
+ has already received the full idle response from
+ client_idle_notify(), which he can now evaluate */
+
+ return COMMAND_RETURN_OK;
+ } else if (client->idle_waiting) {
+ /* during idle mode, clients must not send anything
+ except "noidle" */
+ g_warning("[%u] command \"%s\" during idle",
+ client->num, line);
+ return COMMAND_RETURN_CLOSE;
+ }
+
+ if (client->cmd_list.IsActive()) {
+ if (strcmp(line, CLIENT_LIST_MODE_END) == 0) {
+ g_debug("[%u] process command list",
+ client->num);
+
+ auto &&cmd_list = client->cmd_list.Commit();
+
+ ret = client_process_command_list(client,
+ client->cmd_list.IsOKMode(),
+ std::move(cmd_list));
+ g_debug("[%u] process command "
+ "list returned %i", client->num, ret);
+
+ if (ret == COMMAND_RETURN_CLOSE ||
+ client->IsExpired())
+ return COMMAND_RETURN_CLOSE;
+
+ if (ret == COMMAND_RETURN_OK)
+ command_success(client);
+
+ client->cmd_list.Reset();
+ } else {
+ if (!client->cmd_list.Add(line)) {
+ g_warning("[%u] command list size "
+ "is larger than the max (%lu)",
+ client->num,
+ (unsigned long)client_max_command_list_size);
+ return COMMAND_RETURN_CLOSE;
+ }
+
+ ret = COMMAND_RETURN_OK;
+ }
+ } else {
+ if (strcmp(line, CLIENT_LIST_MODE_BEGIN) == 0) {
+ client->cmd_list.Begin(false);
+ ret = COMMAND_RETURN_OK;
+ } else if (strcmp(line, CLIENT_LIST_OK_MODE_BEGIN) == 0) {
+ client->cmd_list.Begin(true);
+ ret = COMMAND_RETURN_OK;
+ } else {
+ g_debug("[%u] process command \"%s\"",
+ client->num, line);
+ ret = command_process(client, 0, line);
+ g_debug("[%u] command returned %i",
+ client->num, ret);
+
+ if (ret == COMMAND_RETURN_CLOSE ||
+ client->IsExpired())
+ return COMMAND_RETURN_CLOSE;
+
+ if (ret == COMMAND_RETURN_OK)
+ command_success(client);
+ }
+ }
+
+ return ret;
+}
diff --git a/src/ClientRead.cxx b/src/ClientRead.cxx
new file mode 100644
index 000000000..49c698bc1
--- /dev/null
+++ b/src/ClientRead.cxx
@@ -0,0 +1,66 @@
+/*
+ * 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 "ClientInternal.hxx"
+#include "Main.hxx"
+#include "event/Loop.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+BufferedSocket::InputResult
+Client::OnSocketInput(const void *data, size_t length)
+{
+ const char *p = (const char *)data;
+ const char *newline = (const char *)memchr(p, '\n', length);
+ if (newline == NULL)
+ return InputResult::MORE;
+
+ TimeoutMonitor::ScheduleSeconds(client_timeout);
+
+ char *line = g_strndup(p, newline - p);
+ BufferedSocket::ConsumeInput(newline + 1 - p);
+
+ enum command_return result = client_process_line(this, line);
+ g_free(line);
+
+ switch (result) {
+ case COMMAND_RETURN_OK:
+ case COMMAND_RETURN_IDLE:
+ case COMMAND_RETURN_ERROR:
+ break;
+
+ case COMMAND_RETURN_KILL:
+ Close();
+ main_loop->Break();
+ return InputResult::CLOSED;
+
+ case COMMAND_RETURN_CLOSE:
+ Close();
+ return InputResult::CLOSED;
+ }
+
+ if (IsExpired()) {
+ Close();
+ return InputResult::CLOSED;
+ }
+
+ return InputResult::AGAIN;
+}
diff --git a/src/ClientSubscribe.cxx b/src/ClientSubscribe.cxx
new file mode 100644
index 000000000..918a621db
--- /dev/null
+++ b/src/ClientSubscribe.cxx
@@ -0,0 +1,92 @@
+/*
+ * 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 "ClientSubscribe.hxx"
+#include "ClientInternal.hxx"
+#include "Idle.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+enum client_subscribe_result
+client_subscribe(Client *client, const char *channel)
+{
+ assert(client != NULL);
+ assert(channel != NULL);
+
+ if (!client_message_valid_channel_name(channel))
+ return CLIENT_SUBSCRIBE_INVALID;
+
+ if (client->num_subscriptions >= CLIENT_MAX_SUBSCRIPTIONS)
+ return CLIENT_SUBSCRIBE_FULL;
+
+ auto r = client->subscriptions.insert(channel);
+ if (!r.second)
+ return CLIENT_SUBSCRIBE_ALREADY;
+
+ ++client->num_subscriptions;
+
+ idle_add(IDLE_SUBSCRIPTION);
+
+ return CLIENT_SUBSCRIBE_OK;
+}
+
+bool
+client_unsubscribe(Client *client, const char *channel)
+{
+ const auto i = client->subscriptions.find(channel);
+ if (i == client->subscriptions.end())
+ return false;
+
+ assert(client->num_subscriptions > 0);
+
+ client->subscriptions.erase(i);
+ --client->num_subscriptions;
+
+ idle_add(IDLE_SUBSCRIPTION);
+
+ assert((client->num_subscriptions == 0) ==
+ client->subscriptions.empty());
+
+ return true;
+}
+
+void
+client_unsubscribe_all(Client *client)
+{
+ client->subscriptions.clear();
+ client->num_subscriptions = 0;
+}
+
+bool
+client_push_message(Client *client, const ClientMessage &msg)
+{
+ assert(client != NULL);
+
+ if (client->messages.size() >= CLIENT_MAX_MESSAGES ||
+ !client->IsSubscribed(msg.GetChannel()))
+ return false;
+
+ if (client->messages.empty())
+ client->IdleAdd(IDLE_MESSAGE);
+
+ client->messages.push_back(msg);
+ return true;
+}
diff --git a/src/ClientSubscribe.hxx b/src/ClientSubscribe.hxx
new file mode 100644
index 000000000..83c234db6
--- /dev/null
+++ b/src/ClientSubscribe.hxx
@@ -0,0 +1,54 @@
+/*
+ * 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_CLIENT_SUBSCRIBE_HXX
+#define MPD_CLIENT_SUBSCRIBE_HXX
+
+#include "gcc.h"
+
+class Client;
+class ClientMessage;
+
+enum client_subscribe_result {
+ /** success */
+ CLIENT_SUBSCRIBE_OK,
+
+ /** invalid channel name */
+ CLIENT_SUBSCRIBE_INVALID,
+
+ /** already subscribed to this channel */
+ CLIENT_SUBSCRIBE_ALREADY,
+
+ /** too many subscriptions */
+ CLIENT_SUBSCRIBE_FULL,
+};
+
+enum client_subscribe_result
+client_subscribe(Client *client, const char *channel);
+
+bool
+client_unsubscribe(Client *client, const char *channel);
+
+void
+client_unsubscribe_all(Client *client);
+
+bool
+client_push_message(Client *client, const ClientMessage &msg);
+
+#endif
diff --git a/src/ClientWrite.cxx b/src/ClientWrite.cxx
new file mode 100644
index 000000000..23b515a3d
--- /dev/null
+++ b/src/ClientWrite.cxx
@@ -0,0 +1,87 @@
+/*
+ * 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 "ClientInternal.hxx"
+
+#include <string.h>
+#include <stdio.h>
+
+/**
+ * Write a block of data to the client.
+ */
+static void
+client_write(Client *client, const char *data, size_t length)
+{
+ /* if the client is going to be closed, do nothing */
+ if (client->IsExpired() || length == 0)
+ return;
+
+ client->Write(data, length);
+}
+
+void
+client_puts(Client *client, const char *s)
+{
+ client_write(client, s, strlen(s));
+}
+
+void
+client_vprintf(Client *client, const char *fmt, va_list args)
+{
+#ifndef G_OS_WIN32
+ va_list tmp;
+ int length;
+
+ va_copy(tmp, args);
+ length = vsnprintf(NULL, 0, fmt, tmp);
+ va_end(tmp);
+
+ if (length <= 0)
+ /* wtf.. */
+ return;
+
+ char *buffer = (char *)g_malloc(length + 1);
+ vsnprintf(buffer, length + 1, fmt, args);
+ client_write(client, buffer, length);
+ g_free(buffer);
+#else
+ /* On mingw32, snprintf() expects a 64 bit integer instead of
+ a "long int" for "%li". This is not consistent with our
+ expectation, so we're using plain sprintf() here, hoping
+ the static buffer is large enough. Sorry for this hack,
+ but WIN32 development is so painful, I'm not in the mood to
+ do it properly now. */
+
+ static char buffer[4096];
+ vsprintf(buffer, fmt, args);
+ client_write(client, buffer, strlen(buffer));
+#endif
+}
+
+G_GNUC_PRINTF(2, 3)
+void
+client_printf(Client *client, const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ client_vprintf(client, fmt, args);
+ va_end(args);
+}
diff --git a/src/CommandError.cxx b/src/CommandError.cxx
new file mode 100644
index 000000000..7e777d82a
--- /dev/null
+++ b/src/CommandError.cxx
@@ -0,0 +1,134 @@
+/*
+ * 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 "CommandError.hxx"
+#include "db_error.h"
+#include "io_error.h"
+#include "protocol/Result.hxx"
+
+#include <assert.h>
+#include <errno.h>
+
+enum command_return
+print_playlist_result(Client *client, enum playlist_result result)
+{
+ switch (result) {
+ case PLAYLIST_RESULT_SUCCESS:
+ return COMMAND_RETURN_OK;
+
+ case PLAYLIST_RESULT_ERRNO:
+ command_error(client, ACK_ERROR_SYSTEM, "%s",
+ g_strerror(errno));
+ return COMMAND_RETURN_ERROR;
+
+ case PLAYLIST_RESULT_DENIED:
+ command_error(client, ACK_ERROR_PERMISSION, "Access denied");
+ return COMMAND_RETURN_ERROR;
+
+ case PLAYLIST_RESULT_NO_SUCH_SONG:
+ command_error(client, ACK_ERROR_NO_EXIST, "No such song");
+ return COMMAND_RETURN_ERROR;
+
+ case PLAYLIST_RESULT_NO_SUCH_LIST:
+ command_error(client, ACK_ERROR_NO_EXIST, "No such playlist");
+ return COMMAND_RETURN_ERROR;
+
+ case PLAYLIST_RESULT_LIST_EXISTS:
+ command_error(client, ACK_ERROR_EXIST,
+ "Playlist already exists");
+ return COMMAND_RETURN_ERROR;
+
+ case PLAYLIST_RESULT_BAD_NAME:
+ command_error(client, ACK_ERROR_ARG,
+ "playlist name is invalid: "
+ "playlist names may not contain slashes,"
+ " newlines or carriage returns");
+ return COMMAND_RETURN_ERROR;
+
+ case PLAYLIST_RESULT_BAD_RANGE:
+ command_error(client, ACK_ERROR_ARG, "Bad song index");
+ return COMMAND_RETURN_ERROR;
+
+ case PLAYLIST_RESULT_NOT_PLAYING:
+ command_error(client, ACK_ERROR_PLAYER_SYNC, "Not playing");
+ return COMMAND_RETURN_ERROR;
+
+ case PLAYLIST_RESULT_TOO_LARGE:
+ command_error(client, ACK_ERROR_PLAYLIST_MAX,
+ "playlist is at the max size");
+ return COMMAND_RETURN_ERROR;
+
+ case PLAYLIST_RESULT_DISABLED:
+ command_error(client, ACK_ERROR_UNKNOWN,
+ "stored playlist support is disabled");
+ return COMMAND_RETURN_ERROR;
+ }
+
+ assert(0);
+ return COMMAND_RETURN_ERROR;
+}
+
+/**
+ * Send the GError to the client and free the GError.
+ */
+enum command_return
+print_error(Client *client, GError *error)
+{
+ assert(client != NULL);
+ assert(error != NULL);
+
+ g_warning("%s", error->message);
+
+ if (error->domain == playlist_quark()) {
+ enum playlist_result result = (playlist_result)error->code;
+ g_error_free(error);
+ return print_playlist_result(client, result);
+ } else if (error->domain == ack_quark()) {
+ command_error(client, (ack)error->code, "%s", error->message);
+ g_error_free(error);
+ return COMMAND_RETURN_ERROR;
+ } else if (error->domain == db_quark()) {
+ switch ((enum db_error)error->code) {
+ case DB_DISABLED:
+ command_error(client, ACK_ERROR_NO_EXIST, "%s",
+ error->message);
+ g_error_free(error);
+ return COMMAND_RETURN_ERROR;
+
+ case DB_NOT_FOUND:
+ g_error_free(error);
+ command_error(client, ACK_ERROR_NO_EXIST, "Not found");
+ return COMMAND_RETURN_ERROR;
+ }
+ } else if (error->domain == errno_quark()) {
+ command_error(client, ACK_ERROR_SYSTEM, "%s",
+ g_strerror(error->code));
+ g_error_free(error);
+ return COMMAND_RETURN_ERROR;
+ } else if (error->domain == g_file_error_quark()) {
+ command_error(client, ACK_ERROR_SYSTEM, "%s", error->message);
+ g_error_free(error);
+ return COMMAND_RETURN_ERROR;
+ }
+
+ g_error_free(error);
+ command_error(client, ACK_ERROR_UNKNOWN, "error");
+ return COMMAND_RETURN_ERROR;
+}
diff --git a/src/CommandError.hxx b/src/CommandError.hxx
new file mode 100644
index 000000000..5fb021220
--- /dev/null
+++ b/src/CommandError.hxx
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+#ifndef MPD_COMMAND_ERROR_HXX
+#define MPD_COMMAND_ERROR_HXX
+
+#include "command.h"
+#include "playlist_error.h"
+#include "gerror.h"
+
+class Client;
+
+enum command_return
+print_playlist_result(Client *client, enum playlist_result result);
+
+/**
+ * Send the GError to the client and free the GError.
+ */
+enum command_return
+print_error(Client *client, GError *error);
+
+#endif
diff --git a/src/CommandLine.cxx b/src/CommandLine.cxx
new file mode 100644
index 000000000..1e0e028ec
--- /dev/null
+++ b/src/CommandLine.cxx
@@ -0,0 +1,248 @@
+/*
+ * 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 "CommandLine.hxx"
+#include "ls.hxx"
+#include "Log.hxx"
+#include "conf.h"
+#include "DecoderList.hxx"
+#include "DecoderPlugin.hxx"
+#include "OutputList.hxx"
+#include "OutputPlugin.hxx"
+#include "InputRegistry.hxx"
+#include "InputPlugin.hxx"
+#include "PlaylistRegistry.hxx"
+#include "PlaylistPlugin.hxx"
+#include "mpd_error.h"
+#include "fs/Path.hxx"
+#include "fs/FileSystem.hxx"
+
+#ifdef ENABLE_ENCODER
+#include "EncoderList.hxx"
+#include "EncoderPlugin.hxx"
+#endif
+
+#ifdef ENABLE_ARCHIVE
+#include "ArchiveList.hxx"
+#include "ArchivePlugin.hxx"
+#endif
+
+#include <glib.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#ifdef G_OS_WIN32
+#define CONFIG_FILE_LOCATION "\\mpd\\mpd.conf"
+#else /* G_OS_WIN32 */
+#define USER_CONFIG_FILE_LOCATION1 ".mpdconf"
+#define USER_CONFIG_FILE_LOCATION2 ".mpd/mpd.conf"
+#endif
+
+static GQuark
+cmdline_quark(void)
+{
+ return g_quark_from_static_string("cmdline");
+}
+
+G_GNUC_NORETURN
+static void version(void)
+{
+ puts(PACKAGE " (MPD: Music Player Daemon) " VERSION " \n"
+ "\n"
+ "Copyright (C) 2003-2007 Warren Dukes <warren.dukes@gmail.com>\n"
+ "Copyright (C) 2008-2012 Max Kellermann <max@duempel.org>\n"
+ "This is free software; see the source for copying conditions. There is NO\n"
+ "warranty; not even MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
+ "\n"
+ "Decoders plugins:");
+
+ decoder_plugins_for_each(plugin) {
+ printf(" [%s]", plugin->name);
+
+ const char *const*suffixes = plugin->suffixes;
+ if (suffixes != NULL)
+ for (; *suffixes != NULL; ++suffixes)
+ printf(" %s", *suffixes);
+
+ puts("");
+ }
+
+ puts("\n"
+ "Output plugins:");
+ audio_output_plugins_for_each(plugin)
+ printf(" %s", plugin->name);
+ puts("");
+
+#ifdef ENABLE_ENCODER
+ puts("\n"
+ "Encoder plugins:");
+ encoder_plugins_for_each(plugin)
+ printf(" %s", plugin->name);
+ puts("");
+#endif
+
+#ifdef ENABLE_ARCHIVE
+ puts("\n"
+ "Archive plugins:");
+ archive_plugins_for_each(plugin) {
+ printf(" [%s]", plugin->name);
+
+ const char *const*suffixes = plugin->suffixes;
+ if (suffixes != NULL)
+ for (; *suffixes != NULL; ++suffixes)
+ printf(" %s", *suffixes);
+
+ puts("");
+ }
+#endif
+
+ puts("\n"
+ "Input plugins:");
+ input_plugins_for_each(plugin)
+ printf(" %s", plugin->name);
+
+ puts("\n\n"
+ "Playlist plugins:");
+ playlist_plugins_for_each(plugin)
+ printf(" %s", plugin->name);
+
+ puts("\n\n"
+ "Protocols:");
+ print_supported_uri_schemes_to_fp(stdout);
+
+ exit(EXIT_SUCCESS);
+}
+
+static const char *summary =
+ "Music Player Daemon - a daemon for playing music.";
+
+gcc_pure
+static Path
+PathBuildChecked(const Path &a, Path::const_pointer b)
+{
+ if (a.IsNull())
+ return Path::Null();
+
+ return Path::Build(a, b);
+}
+
+bool
+parse_cmdline(int argc, char **argv, struct options *options,
+ GError **error_r)
+{
+ GError *error = NULL;
+ 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", NULL },
+ { "no-config", 0, 0, G_OPTION_ARG_NONE, &option_no_config,
+ "don't read from config", NULL },
+ { "no-daemon", 0, 0, G_OPTION_ARG_NONE, &option_no_daemon,
+ "don't detach from console", NULL },
+ { "stdout", 0, 0, G_OPTION_ARG_NONE, &options->log_stderr,
+ NULL, NULL },
+ { "stderr", 0, 0, G_OPTION_ARG_NONE, &options->log_stderr,
+ "print messages to stderr", NULL },
+ { "verbose", 'v', 0, G_OPTION_ARG_NONE, &options->verbose,
+ "verbose logging", NULL },
+ { "version", 'V', 0, G_OPTION_ARG_NONE, &option_version,
+ "print version number", NULL },
+ { nullptr, 0, 0, G_OPTION_ARG_NONE, nullptr, nullptr, nullptr }
+ };
+
+ 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, NULL);
+
+ g_option_context_set_summary(context, summary);
+
+ ret = g_option_context_parse(context, &argc, &argv, &error);
+ g_option_context_free(context);
+
+ if (!ret)
+ MPD_ERROR("option parsing failed: %s\n", error->message);
+
+ if (option_version)
+ version();
+
+ /* 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) {
+ g_debug("Ignoring config, using daemon defaults\n");
+ return true;
+ } else if (argc <= 1) {
+ /* default configuration file path */
+
+#ifdef G_OS_WIN32
+ Path path = PathBuildChecked(Path::FromUTF8(g_get_user_config_dir()),
+ CONFIG_FILE_LOCATION);
+ if (!path.IsNull() && FileExists(path))
+ return ReadConfigFile(path, error_r);
+
+ const char *const*system_config_dirs =
+ g_get_system_config_dirs();
+
+ for (unsigned i = 0; system_config_dirs[i] != nullptr; ++i) {
+ path = PathBuildChecked(Path::FromUTF8(system_config_dirs[i]),
+ CONFIG_FILE_LOCATION);
+ if (!path.IsNull() && FileExists(path))
+ return ReadConfigFile(path, error_r);
+ }
+#else /* G_OS_WIN32 */
+ Path path = PathBuildChecked(Path::FromUTF8(g_get_home_dir()),
+ USER_CONFIG_FILE_LOCATION1);
+ if (!path.IsNull() && FileExists(path))
+ return ReadConfigFile(path, error_r);
+
+ path = PathBuildChecked(Path::FromUTF8(g_get_home_dir()),
+ USER_CONFIG_FILE_LOCATION2);
+ if (!path.IsNull() && FileExists(path))
+ return ReadConfigFile(path, error_r);
+
+ path = Path::FromUTF8(SYSTEM_CONFIG_FILE_LOCATION);
+ if (!path.IsNull() && FileExists(path))
+ return ReadConfigFile(path, error_r);
+#endif
+
+ g_set_error(error_r, cmdline_quark(), 0,
+ "No configuration file found");
+ return false;
+ } else if (argc == 2) {
+ /* specified configuration file */
+ return ReadConfigFile(Path::FromFS(argv[1]), error_r);
+ } else {
+ g_set_error(error_r, cmdline_quark(), 0,
+ "too many arguments");
+ return false;
+ }
+}
diff --git a/src/CommandLine.hxx b/src/CommandLine.hxx
new file mode 100644
index 000000000..7a8731f82
--- /dev/null
+++ b/src/CommandLine.hxx
@@ -0,0 +1,36 @@
+/*
+ * 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_COMMAND_LINE_HXX
+#define MPD_COMMAND_LINE_HXX
+
+#include <glib.h>
+
+struct options {
+ gboolean kill;
+ gboolean daemon;
+ gboolean log_stderr;
+ gboolean verbose;
+};
+
+bool
+parse_cmdline(int argc, char **argv, struct options *options,
+ GError **error_r);
+
+#endif
diff --git a/src/CommandListBuilder.cxx b/src/CommandListBuilder.cxx
new file mode 100644
index 000000000..cc10f7205
--- /dev/null
+++ b/src/CommandListBuilder.cxx
@@ -0,0 +1,43 @@
+/*
+ * 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 "CommandListBuilder.hxx"
+#include "ClientInternal.hxx"
+
+#include <string.h>
+
+void
+CommandListBuilder::Reset()
+{
+ list.clear();
+ mode = Mode::DISABLED;
+}
+
+bool
+CommandListBuilder::Add(const char *cmd)
+{
+ size_t len = strlen(cmd) + 1;
+ size += len;
+ if (size > client_max_command_list_size)
+ return false;
+
+ list.emplace_back(cmd);
+ return true;
+}
diff --git a/src/CommandListBuilder.hxx b/src/CommandListBuilder.hxx
new file mode 100644
index 000000000..a112ac33b
--- /dev/null
+++ b/src/CommandListBuilder.hxx
@@ -0,0 +1,109 @@
+/*
+ * 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_COMMAND_LIST_BUILDER_HXX
+#define MPD_COMMAND_LIST_BUILDER_HXX
+
+#include <list>
+#include <string>
+
+#include <assert.h>
+
+class CommandListBuilder {
+ /**
+ * print OK after each command execution
+ */
+ enum class Mode {
+ /**
+ * Not active.
+ */
+ DISABLED = -1,
+
+ /**
+ * Enabled in normal list mode.
+ */
+ ENABLED = false,
+
+ /**
+ * Enabled in "list_OK" mode.
+ */
+ OK = true,
+ } mode;
+
+ /**
+ * for when in list mode
+ */
+ std::list<std::string> list;
+
+ /**
+ * Memory consumed by the list.
+ */
+ size_t size;
+
+public:
+ CommandListBuilder()
+ :mode(Mode::DISABLED), size(0) {}
+
+ /**
+ * Is a command list currently being built?
+ */
+ bool IsActive() const {
+ return mode != Mode::DISABLED;
+ }
+
+ /**
+ * Is the object in "list_OK" mode?
+ */
+ bool IsOKMode() const {
+ assert(IsActive());
+
+ return (bool)mode;
+ }
+
+ /**
+ * Reset the object: delete the list and clear the mode.
+ */
+ void Reset();
+
+ /**
+ * Begin building a command list.
+ */
+ void Begin(bool ok) {
+ assert(list.empty());
+ assert(mode == Mode::DISABLED);
+
+ mode = (Mode)ok;
+ }
+
+ /**
+ * @return false if the list is full
+ */
+ bool Add(const char *cmd);
+
+ /**
+ * Finishes the list and returns it.
+ */
+ std::list<std::string> &&Commit() {
+ assert(IsActive());
+
+ return std::move(list);
+ }
+};
+
+#endif
diff --git a/src/ConfigData.cxx b/src/ConfigData.cxx
new file mode 100644
index 000000000..7ac517935
--- /dev/null
+++ b/src/ConfigData.cxx
@@ -0,0 +1,133 @@
+/*
+ * 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 "ConfigData.hxx"
+#include "ConfigParser.hxx"
+#include "ConfigPath.hxx"
+#include "mpd_error.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+
+unsigned
+block_param::GetUnsignedValue() const
+{
+ char *endptr;
+ long value2 = strtol(value.c_str(), &endptr, 0);
+ if (*endptr != 0)
+ MPD_ERROR("Not a valid number in line %i", line);
+
+ if (value2 < 0)
+ MPD_ERROR("Not a positive number in line %i", line);
+
+ return (unsigned)value2;
+}
+
+bool
+block_param::GetBoolValue() const
+{
+ bool value2;
+ if (!get_bool(value.c_str(), &value2))
+ MPD_ERROR("%s is not a boolean value (yes, true, 1) or "
+ "(no, false, 0) on line %i\n",
+ name.c_str(), line);
+
+ return value2;
+}
+
+config_param::config_param(const char *_value, int _line)
+ :next(nullptr), value(g_strdup(_value)), line(_line) {}
+
+config_param::~config_param()
+{
+ delete next;
+ g_free(value);
+}
+
+const block_param *
+config_param::GetBlockParam(const char *name) const
+{
+ for (const auto &i : block_params) {
+ if (i.name == name) {
+ i.used = true;
+ return &i;
+ }
+ }
+
+ return NULL;
+}
+
+const char *
+config_param::GetBlockValue(const char *name, const char *default_value) const
+{
+ const block_param *bp = GetBlockParam(name);
+ if (bp == nullptr)
+ return default_value;
+
+ return bp->value.c_str();
+}
+
+char *
+config_param::DupBlockString(const char *name, const char *default_value) const
+{
+ return g_strdup(GetBlockValue(name, default_value));
+}
+
+char *
+config_param::DupBlockPath(const char *name, GError **error_r) const
+{
+ assert(error_r != nullptr);
+ assert(*error_r == nullptr);
+
+ const block_param *bp = GetBlockParam(name);
+ if (bp == nullptr)
+ return nullptr;
+
+ char *path = parsePath(bp->value.c_str(), error_r);
+ if (G_UNLIKELY(path == nullptr))
+ g_prefix_error(error_r,
+ "Invalid path in \"%s\" at line %i: ",
+ name, bp->line);
+
+ return path;
+}
+
+unsigned
+config_param::GetBlockValue(const char *name, unsigned default_value) const
+{
+ const block_param *bp = GetBlockParam(name);
+ if (bp == nullptr)
+ return default_value;
+
+ return bp->GetUnsignedValue();
+}
+
+gcc_pure
+bool
+config_param::GetBlockValue(const char *name, bool default_value) const
+{
+ const block_param *bp = GetBlockParam(name);
+ if (bp == NULL)
+ return default_value;
+
+ return bp->GetBoolValue();
+}
diff --git a/src/ConfigData.hxx b/src/ConfigData.hxx
new file mode 100644
index 000000000..2586ed4b2
--- /dev/null
+++ b/src/ConfigData.hxx
@@ -0,0 +1,127 @@
+/*
+ * 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_CONFIG_DATA_HXX
+#define MPD_CONFIG_DATA_HXX
+
+#include "ConfigOption.hxx"
+#include "gerror.h"
+#include "gcc.h"
+
+#include <string>
+#include <array>
+#include <vector>
+
+struct block_param {
+ std::string name;
+ std::string value;
+ int line;
+
+ /**
+ * This flag is false when nobody has queried the value of
+ * this option yet.
+ */
+ mutable bool used;
+
+ gcc_nonnull_all
+ block_param(const char *_name, const char *_value, int _line=-1)
+ :name(_name), value(_value), line(_line), used(false) {}
+
+ gcc_pure
+ unsigned GetUnsignedValue() const;
+
+ gcc_pure
+ bool GetBoolValue() const;
+};
+
+struct config_param {
+ /**
+ * The next config_param with the same name. The destructor
+ * deletes the whole chain.
+ */
+ struct config_param *next;
+
+ char *value;
+ unsigned int line;
+
+ std::vector<block_param> block_params;
+
+ /**
+ * This flag is false when nobody has queried the value of
+ * this option yet.
+ */
+ bool used;
+
+ config_param(int _line=-1)
+ :next(nullptr), value(nullptr), line(_line), used(false) {}
+
+ gcc_nonnull_all
+ config_param(const char *_value, int _line=-1);
+
+ config_param(const config_param &) = delete;
+
+ ~config_param();
+
+ config_param &operator=(const config_param &) = delete;
+
+ /**
+ * Determine if this is a "null" instance, i.e. an empty
+ * object that was synthesized and not loaded from a
+ * configuration file.
+ */
+ bool IsNull() const {
+ return line == unsigned(-1);
+ }
+
+ gcc_nonnull_all
+ void AddBlockParam(const char *_name, const char *_value,
+ int _line=-1) {
+ block_params.emplace_back(_name, _value, _line);
+ }
+
+ gcc_nonnull_all gcc_pure
+ const block_param *GetBlockParam(const char *_name) const;
+
+ gcc_pure
+ const char *GetBlockValue(const char *name,
+ const char *default_value=nullptr) const;
+
+ gcc_malloc
+ char *DupBlockString(const char *name,
+ const char *default_value=nullptr) const;
+
+ /**
+ * Same as config_dup_path(), but looks up the setting in the
+ * specified block.
+ */
+ gcc_malloc
+ char *DupBlockPath(const char *name, GError **error_r) const;
+
+ gcc_pure
+ unsigned GetBlockValue(const char *name, unsigned default_value) const;
+
+ gcc_pure
+ bool GetBlockValue(const char *name, bool default_value) const;
+};
+
+struct ConfigData {
+ std::array<config_param *, std::size_t(CONF_MAX)> params;
+};
+
+#endif
diff --git a/src/ConfigFile.cxx b/src/ConfigFile.cxx
new file mode 100644
index 000000000..2573f66e5
--- /dev/null
+++ b/src/ConfigFile.cxx
@@ -0,0 +1,285 @@
+/*
+ * 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 "ConfigFile.hxx"
+#include "ConfigQuark.hxx"
+#include "ConfigData.hxx"
+#include "ConfigTemplates.hxx"
+#include "conf.h"
+#include "util/Tokenizer.hxx"
+#include "util/StringUtil.hxx"
+#include "fs/Path.hxx"
+#include "fs/FileSystem.hxx"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "config"
+
+#define MAX_STRING_SIZE MPD_PATH_MAX+80
+
+#define CONF_COMMENT '#'
+
+static bool
+config_read_name_value(struct config_param *param, char *input, unsigned line,
+ GError **error_r)
+{
+ Tokenizer tokenizer(input);
+
+ const char *name = tokenizer.NextWord(error_r);
+ if (name == NULL) {
+ assert(!tokenizer.IsEnd());
+ return false;
+ }
+
+ const char *value = tokenizer.NextString(error_r);
+ if (value == NULL) {
+ if (tokenizer.IsEnd()) {
+ assert(error_r == NULL || *error_r == NULL);
+ g_set_error(error_r, config_quark(), 0,
+ "Value missing");
+ } else {
+ assert(error_r == NULL || *error_r != NULL);
+ }
+
+ return false;
+ }
+
+ if (!tokenizer.IsEnd() && tokenizer.CurrentChar() != CONF_COMMENT) {
+ g_set_error(error_r, config_quark(), 0,
+ "Unknown tokens after value");
+ return false;
+ }
+
+ const struct block_param *bp = param->GetBlockParam(name);
+ if (bp != NULL) {
+ g_set_error(error_r, config_quark(), 0,
+ "\"%s\" is duplicate, first defined on line %i",
+ name, bp->line);
+ return false;
+ }
+
+ param->AddBlockParam(name, value, line);
+ return true;
+}
+
+static struct config_param *
+config_read_block(FILE *fp, int *count, char *string, GError **error_r)
+{
+ struct config_param *ret = new config_param(*count);
+ GError *error = NULL;
+
+ while (true) {
+ char *line;
+
+ line = fgets(string, MAX_STRING_SIZE, fp);
+ if (line == NULL) {
+ delete ret;
+ g_set_error(error_r, config_quark(), 0,
+ "Expected '}' before end-of-file");
+ return NULL;
+ }
+
+ (*count)++;
+ line = strchug_fast(line);
+ if (*line == 0 || *line == CONF_COMMENT)
+ continue;
+
+ if (*line == '}') {
+ /* end of this block; return from the function
+ (and from this "while" loop) */
+
+ line = strchug_fast(line + 1);
+ if (*line != 0 && *line != CONF_COMMENT) {
+ delete ret;
+ g_set_error(error_r, config_quark(), 0,
+ "line %i: Unknown tokens after '}'",
+ *count);
+ return nullptr;
+ }
+
+ return ret;
+ }
+
+ /* parse name and value */
+
+ if (!config_read_name_value(ret, line, *count, &error)) {
+ assert(*line != 0);
+ delete ret;
+ g_propagate_prefixed_error(error_r, error,
+ "line %i: ", *count);
+ return NULL;
+ }
+ }
+}
+
+gcc_nonnull_all
+static void
+Append(config_param *&head, config_param *p)
+{
+ assert(p->next == nullptr);
+
+ config_param **i = &head;
+ while (*i != nullptr)
+ i = &(*i)->next;
+
+ *i = p;
+}
+
+static bool
+ReadConfigFile(ConfigData &config_data, FILE *fp, GError **error_r)
+{
+ assert(fp != nullptr);
+
+ char string[MAX_STRING_SIZE + 1];
+ int count = 0;
+ struct config_param *param;
+
+ while (fgets(string, MAX_STRING_SIZE, fp)) {
+ char *line;
+ const char *name, *value;
+ GError *error = NULL;
+
+ count++;
+
+ line = strchug_fast(string);
+ if (*line == 0 || *line == CONF_COMMENT)
+ continue;
+
+ /* the first token in each line is the name, followed
+ by either the value or '{' */
+
+ Tokenizer tokenizer(line);
+ name = tokenizer.NextWord(&error);
+ if (name == NULL) {
+ assert(!tokenizer.IsEnd());
+ g_propagate_prefixed_error(error_r, error,
+ "line %i: ", count);
+ return false;
+ }
+
+ /* get the definition of that option, and check the
+ "repeatable" flag */
+
+ const ConfigOption o = ParseConfigOptionName(name);
+ if (o == CONF_MAX) {
+ g_set_error(error_r, config_quark(), 0,
+ "unrecognized parameter in config file at "
+ "line %i: %s\n", count, name);
+ return false;
+ }
+
+ const unsigned i = unsigned(o);
+ const ConfigTemplate &option = config_templates[i];
+ config_param *&head = config_data.params[i];
+
+ if (head != nullptr && !option.repeatable) {
+ param = head;
+ g_set_error(error_r, config_quark(), 0,
+ "config parameter \"%s\" is first defined "
+ "on line %i and redefined on line %i\n",
+ name, param->line, count);
+ return false;
+ }
+
+ /* now parse the block or the value */
+
+ if (option.block) {
+ /* it's a block, call config_read_block() */
+
+ if (tokenizer.CurrentChar() != '{') {
+ g_set_error(error_r, config_quark(), 0,
+ "line %i: '{' expected", count);
+ return false;
+ }
+
+ line = strchug_fast(tokenizer.Rest() + 1);
+ if (*line != 0 && *line != CONF_COMMENT) {
+ g_set_error(error_r, config_quark(), 0,
+ "line %i: Unknown tokens after '{'",
+ count);
+ return false;
+ }
+
+ param = config_read_block(fp, &count, string, error_r);
+ if (param == NULL) {
+ return false;
+ }
+ } else {
+ /* a string value */
+
+ value = tokenizer.NextString(&error);
+ if (value == NULL) {
+ if (tokenizer.IsEnd())
+ g_set_error(error_r, config_quark(), 0,
+ "line %i: Value missing",
+ count);
+ else {
+ g_set_error(error_r, config_quark(), 0,
+ "line %i: %s", count,
+ error->message);
+ g_error_free(error);
+ }
+
+ return false;
+ }
+
+ if (!tokenizer.IsEnd() &&
+ tokenizer.CurrentChar() != CONF_COMMENT) {
+ g_set_error(error_r, config_quark(), 0,
+ "line %i: Unknown tokens after value",
+ count);
+ return false;
+ }
+
+ param = new config_param(value, count);
+ }
+
+ Append(head, param);
+ }
+
+ return true;
+}
+
+bool
+ReadConfigFile(ConfigData &config_data, const Path &path, GError **error_r)
+{
+ assert(!path.IsNull());
+ const std::string path_utf8 = path.ToUTF8();
+
+ g_debug("loading file %s", path_utf8.c_str());
+
+ FILE *fp = FOpen(path, FOpenMode::ReadText);
+ if (fp == nullptr) {
+ g_set_error(error_r, config_quark(), errno,
+ "Failed to open %s: %s",
+ path_utf8.c_str(), g_strerror(errno));
+ return false;
+ }
+
+ bool result = ReadConfigFile(config_data, fp, error_r);
+ fclose(fp);
+ return result;
+}
diff --git a/src/ConfigFile.hxx b/src/ConfigFile.hxx
new file mode 100644
index 000000000..49c0d31ec
--- /dev/null
+++ b/src/ConfigFile.hxx
@@ -0,0 +1,31 @@
+/*
+ * 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_CONFIG_FILE_HXX
+#define MPD_CONFIG_FILE_HXX
+
+#include "gerror.h"
+
+class Path;
+struct ConfigData;
+
+bool
+ReadConfigFile(ConfigData &data, const Path &path, GError **error_r);
+
+#endif
diff --git a/src/ConfigGlobal.cxx b/src/ConfigGlobal.cxx
new file mode 100644
index 000000000..a66c03748
--- /dev/null
+++ b/src/ConfigGlobal.cxx
@@ -0,0 +1,172 @@
+/*
+ * 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 "ConfigGlobal.hxx"
+#include "ConfigParser.hxx"
+#include "ConfigData.hxx"
+#include "ConfigFile.hxx"
+#include "ConfigPath.hxx"
+#include "mpd_error.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "config"
+
+static ConfigData config_data;
+
+void config_global_finish(void)
+{
+ for (auto i : config_data.params)
+ delete i;
+}
+
+void config_global_init(void)
+{
+}
+
+bool
+ReadConfigFile(const Path &path, GError **error_r)
+{
+ return ReadConfigFile(config_data, path, error_r);
+}
+
+static void
+Check(const config_param *param)
+{
+ if (!param->used)
+ /* this whole config_param was not queried at all -
+ the feature might be disabled at compile time?
+ Silently ignore it here. */
+ return;
+
+ for (const auto &i : param->block_params) {
+ if (!i.used)
+ g_warning("option '%s' on line %i was not recognized",
+ i.name.c_str(), i.line);
+ }
+}
+
+void config_global_check(void)
+{
+ for (auto i : config_data.params)
+ for (const config_param *p = i; p != nullptr; p = p->next)
+ Check(p);
+}
+
+const struct config_param *
+config_get_next_param(ConfigOption option, const struct config_param * last)
+{
+ config_param *param = last != nullptr
+ ? last->next
+ : config_data.params[unsigned(option)];
+ if (param != nullptr)
+ param->used = true;
+ return param;
+}
+
+const char *
+config_get_string(ConfigOption option, const char *default_value)
+{
+ const struct config_param *param = config_get_param(option);
+
+ if (param == NULL)
+ return default_value;
+
+ return param->value;
+}
+
+char *
+config_dup_path(ConfigOption option, GError **error_r)
+{
+ assert(error_r != NULL);
+ assert(*error_r == NULL);
+
+ const struct config_param *param = config_get_param(option);
+ if (param == NULL)
+ return NULL;
+
+ char *path = parsePath(param->value, error_r);
+ if (G_UNLIKELY(path == NULL))
+ g_prefix_error(error_r,
+ "Invalid path at line %i: ",
+ param->line);
+
+ return path;
+}
+
+unsigned
+config_get_unsigned(ConfigOption option, unsigned default_value)
+{
+ const struct config_param *param = config_get_param(option);
+ long value;
+ char *endptr;
+
+ if (param == NULL)
+ return default_value;
+
+ value = strtol(param->value, &endptr, 0);
+ if (*endptr != 0 || value < 0)
+ MPD_ERROR("Not a valid non-negative number in line %i",
+ param->line);
+
+ return (unsigned)value;
+}
+
+unsigned
+config_get_positive(ConfigOption option, unsigned default_value)
+{
+ const struct config_param *param = config_get_param(option);
+ long value;
+ char *endptr;
+
+ if (param == NULL)
+ return default_value;
+
+ value = strtol(param->value, &endptr, 0);
+ if (*endptr != 0)
+ MPD_ERROR("Not a valid number in line %i", param->line);
+
+ if (value <= 0)
+ MPD_ERROR("Not a positive number in line %i", param->line);
+
+ return (unsigned)value;
+}
+
+bool
+config_get_bool(ConfigOption option, bool default_value)
+{
+ const struct config_param *param = config_get_param(option);
+ bool success, value;
+
+ if (param == NULL)
+ return default_value;
+
+ success = get_bool(param->value, &value);
+ if (!success)
+ MPD_ERROR("Expected boolean value (yes, true, 1) or "
+ "(no, false, 0) on line %i\n",
+ param->line);
+
+ return value;
+}
diff --git a/src/ConfigGlobal.hxx b/src/ConfigGlobal.hxx
new file mode 100644
index 000000000..9abfb2b5d
--- /dev/null
+++ b/src/ConfigGlobal.hxx
@@ -0,0 +1,109 @@
+/*
+ * 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_CONFIG_GLOBAL_HXX
+#define MPD_CONFIG_GLOBAL_HXX
+
+#include "ConfigOption.hxx"
+#include "gerror.h"
+#include "gcc.h"
+
+#include <stdbool.h>
+#include <stddef.h>
+
+#define DEFAULT_PLAYLIST_MAX_LENGTH (1024*16)
+#define DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS false
+
+#ifdef __cplusplus
+class Path;
+#endif
+
+void config_global_init(void);
+void config_global_finish(void);
+
+/**
+ * Call this function after all configuration has been evaluated. It
+ * checks for unused parameters, and logs warnings.
+ */
+void config_global_check(void);
+
+#ifdef __cplusplus
+
+bool
+ReadConfigFile(const Path &path, GError **error_r);
+
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* don't free the returned value
+ set _last_ to NULL to get first entry */
+gcc_pure
+const struct config_param *
+config_get_next_param(enum ConfigOption option,
+ const struct config_param *last);
+
+gcc_pure
+static inline const struct config_param *
+config_get_param(enum ConfigOption option)
+{
+ return config_get_next_param(option, NULL);
+}
+
+/* Note on gcc_pure: Some of the functions declared pure are not
+ really pure in strict sense. They have side effect such that they
+ validate parameter's value and signal an error if it's invalid.
+ However, if the argument was already validated or we don't care
+ about the argument at all, this may be ignored so in the end, we
+ should be fine with calling those functions pure. */
+
+gcc_pure
+const char *
+config_get_string(enum ConfigOption option, const char *default_value);
+
+/**
+ * Returns an optional configuration variable which contains an
+ * absolute path. If there is a tilde prefix, it is expanded.
+ * Returns NULL if the value is not present. If the path could not be
+ * parsed, returns NULL and sets the error.
+ *
+ * The return value must be freed with g_free().
+ */
+gcc_malloc
+char *
+config_dup_path(enum ConfigOption option, GError **error_r);
+
+gcc_pure
+unsigned
+config_get_unsigned(enum ConfigOption option, unsigned default_value);
+
+gcc_pure
+unsigned
+config_get_positive(enum ConfigOption option, unsigned default_value);
+
+gcc_pure
+bool config_get_bool(enum ConfigOption option, bool default_value);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/ConfigOption.hxx b/src/ConfigOption.hxx
new file mode 100644
index 000000000..21a3a02e4
--- /dev/null
+++ b/src/ConfigOption.hxx
@@ -0,0 +1,90 @@
+/*
+ * 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_CONFIG_OPTION_HXX
+#define MPD_CONFIG_OPTION_HXX
+
+#include "gcc.h"
+
+enum ConfigOption {
+ CONF_MUSIC_DIR,
+ CONF_PLAYLIST_DIR,
+ CONF_FOLLOW_INSIDE_SYMLINKS,
+ CONF_FOLLOW_OUTSIDE_SYMLINKS,
+ CONF_DB_FILE,
+ CONF_STICKER_FILE,
+ CONF_LOG_FILE,
+ CONF_PID_FILE,
+ CONF_STATE_FILE,
+ CONF_RESTORE_PAUSED,
+ CONF_USER,
+ CONF_GROUP,
+ CONF_BIND_TO_ADDRESS,
+ CONF_PORT,
+ CONF_LOG_LEVEL,
+ CONF_ZEROCONF_NAME,
+ CONF_ZEROCONF_ENABLED,
+ CONF_PASSWORD,
+ CONF_DEFAULT_PERMS,
+ CONF_AUDIO_OUTPUT,
+ CONF_AUDIO_OUTPUT_FORMAT,
+ CONF_MIXER_TYPE,
+ CONF_REPLAYGAIN,
+ CONF_REPLAYGAIN_PREAMP,
+ CONF_REPLAYGAIN_MISSING_PREAMP,
+ CONF_REPLAYGAIN_LIMIT,
+ CONF_VOLUME_NORMALIZATION,
+ CONF_SAMPLERATE_CONVERTER,
+ CONF_AUDIO_BUFFER_SIZE,
+ CONF_BUFFER_BEFORE_PLAY,
+ CONF_HTTP_PROXY_HOST,
+ CONF_HTTP_PROXY_PORT,
+ CONF_HTTP_PROXY_USER,
+ CONF_HTTP_PROXY_PASSWORD,
+ CONF_CONN_TIMEOUT,
+ CONF_MAX_CONN,
+ CONF_MAX_PLAYLIST_LENGTH,
+ CONF_MAX_COMMAND_LIST_SIZE,
+ CONF_MAX_OUTPUT_BUFFER_SIZE,
+ CONF_FS_CHARSET,
+ CONF_ID3V1_ENCODING,
+ CONF_METADATA_TO_USE,
+ CONF_SAVE_ABSOLUTE_PATHS,
+ CONF_DECODER,
+ CONF_INPUT,
+ CONF_GAPLESS_MP3_PLAYBACK,
+ CONF_PLAYLIST_PLUGIN,
+ CONF_AUTO_UPDATE,
+ CONF_AUTO_UPDATE_DEPTH,
+ CONF_DESPOTIFY_USER,
+ CONF_DESPOTIFY_PASSWORD,
+ CONF_DESPOTIFY_HIGH_BITRATE,
+ CONF_AUDIO_FILTER,
+ CONF_DATABASE,
+ CONF_MAX
+};
+
+/**
+ * @return #CONF_MAX if not found
+ */
+gcc_pure
+enum ConfigOption
+ParseConfigOptionName(const char *name);
+
+#endif
diff --git a/src/ConfigParser.cxx b/src/ConfigParser.cxx
new file mode 100644
index 000000000..73381d8a0
--- /dev/null
+++ b/src/ConfigParser.cxx
@@ -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.
+ */
+
+#include "ConfigParser.hxx"
+#include "util/StringUtil.hxx"
+
+bool
+get_bool(const char *value, bool *value_r)
+{
+ static const char *t[] = { "yes", "true", "1", nullptr };
+ static const char *f[] = { "no", "false", "0", nullptr };
+
+ if (string_array_contains(t, value)) {
+ *value_r = true;
+ return true;
+ }
+
+ if (string_array_contains(f, value)) {
+ *value_r = false;
+ return true;
+ }
+
+ return false;
+}
diff --git a/src/ConfigParser.hxx b/src/ConfigParser.hxx
new file mode 100644
index 000000000..00fd42fe3
--- /dev/null
+++ b/src/ConfigParser.hxx
@@ -0,0 +1,26 @@
+/*
+ * 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_CONFIG_PARSER_HXX
+#define MPD_CONFIG_PARSER_HXX
+
+bool
+get_bool(const char *value, bool *value_r);
+
+#endif
diff --git a/src/ConfigPath.cxx b/src/ConfigPath.cxx
new file mode 100644
index 000000000..767115d19
--- /dev/null
+++ b/src/ConfigPath.cxx
@@ -0,0 +1,120 @@
+/*
+ * 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 "ConfigPath.hxx"
+#include "conf.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#ifndef WIN32
+#include <pwd.h>
+#endif
+
+#if HAVE_IPV6 && WIN32
+#include <winsock2.h>
+#endif
+
+#if HAVE_IPV6 && ! WIN32
+#include <sys/socket.h>
+#endif
+
+#ifdef WIN32
+#include <windows.h>
+#endif
+
+G_GNUC_CONST
+static inline GQuark
+parse_path_quark(void)
+{
+ return g_quark_from_static_string("path");
+}
+
+char *
+parsePath(const char *path, G_GNUC_UNUSED GError **error_r)
+{
+ assert(path != nullptr);
+ assert(error_r == nullptr || *error_r == nullptr);
+
+#ifndef WIN32
+ if (!g_path_is_absolute(path) && path[0] != '~') {
+ g_set_error(error_r, parse_path_quark(), 0,
+ "not an absolute path: %s", path);
+ return nullptr;
+ } else if (path[0] == '~') {
+ const char *home;
+
+ if (path[1] == '/' || path[1] == '\0') {
+ const char *user = config_get_string(CONF_USER, nullptr);
+ if (user != nullptr) {
+ struct passwd *passwd = getpwnam(user);
+ if (!passwd) {
+ g_set_error(error_r, parse_path_quark(), 0,
+ "no such user: %s", user);
+ return nullptr;
+ }
+
+ home = passwd->pw_dir;
+ } else {
+ home = g_get_home_dir();
+ if (home == nullptr) {
+ g_set_error_literal(error_r, parse_path_quark(), 0,
+ "problems getting home "
+ "for current user");
+ return nullptr;
+ }
+ }
+
+ ++path;
+ } else {
+ ++path;
+
+ const char *slash = strchr(path, '/');
+ char *user = slash != nullptr
+ ? g_strndup(path, slash - path)
+ : g_strdup(path);
+
+ struct passwd *passwd = getpwnam(user);
+ if (!passwd) {
+ g_set_error(error_r, parse_path_quark(), 0,
+ "no such user: %s", user);
+ g_free(user);
+ return nullptr;
+ }
+
+ g_free(user);
+
+ home = passwd->pw_dir;
+ path = slash;
+ }
+
+ return g_strconcat(home, path, nullptr);
+ } else {
+#endif
+ return g_strdup(path);
+#ifndef WIN32
+ }
+#endif
+}
diff --git a/src/ConfigPath.hxx b/src/ConfigPath.hxx
new file mode 100644
index 000000000..42e51215f
--- /dev/null
+++ b/src/ConfigPath.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_CONFIG_PATH_HXX
+#define MPD_CONFIG_PATH_HXX
+
+#include "gerror.h"
+
+char *
+parsePath(const char *path, GError **error_r);
+
+#endif
diff --git a/src/ConfigQuark.hxx b/src/ConfigQuark.hxx
new file mode 100644
index 000000000..11594f998
--- /dev/null
+++ b/src/ConfigQuark.hxx
@@ -0,0 +1,36 @@
+/*
+ * 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_CONFIG_QUARK_HXX
+#define MPD_CONFIG_QUARK_HXX
+
+#include <glib.h>
+
+/**
+ * A GQuark for GError instances, resulting from malformed
+ * configuration.
+ */
+G_GNUC_CONST
+static inline GQuark
+config_quark(void)
+{
+ return g_quark_from_static_string("config");
+}
+
+#endif
diff --git a/src/ConfigTemplates.cxx b/src/ConfigTemplates.cxx
new file mode 100644
index 000000000..6c6bf1689
--- /dev/null
+++ b/src/ConfigTemplates.cxx
@@ -0,0 +1,96 @@
+/*
+ * 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 "ConfigTemplates.hxx"
+#include "ConfigOption.hxx"
+
+#include <string.h>
+
+const ConfigTemplate config_templates[] = {
+ { "music_directory", false, false },
+ { "playlist_directory", false, false },
+ { "follow_inside_symlinks", false, false },
+ { "follow_outside_symlinks", false, false },
+ { "db_file", false, false },
+ { "sticker_file", false, false },
+ { "log_file", false, false },
+ { "pid_file", false, false },
+ { "state_file", false, false },
+ { "restore_paused", false, false },
+ { "user", false, false },
+ { "group", false, false },
+ { "bind_to_address", true, false },
+ { "port", false, false },
+ { "log_level", false, false },
+ { "zeroconf_name", false, false },
+ { "zeroconf_enabled", false, false },
+ { "password", true, false },
+ { "default_permissions", false, false },
+ { "audio_output", true, true },
+ { "audio_output_format", false, false },
+ { "mixer_type", false, false },
+ { "replaygain", false, false },
+ { "replaygain_preamp", false, false },
+ { "replaygain_missing_preamp", false, false },
+ { "replaygain_limit", false, false },
+ { "volume_normalization", false, false },
+ { "samplerate_converter", false, false },
+ { "audio_buffer_size", false, false },
+ { "buffer_before_play", false, false },
+ { "http_proxy_host", false, false },
+ { "http_proxy_port", false, false },
+ { "http_proxy_user", false, false },
+ { "http_proxy_password", false, false },
+ { "connection_timeout", false, false },
+ { "max_connections", false, false },
+ { "max_playlist_length", false, false },
+ { "max_command_list_size", false, false },
+ { "max_output_buffer_size", false, false },
+ { "filesystem_charset", false, false },
+ { "id3v1_encoding", false, false },
+ { "metadata_to_use", false, false },
+ { "save_absolute_paths_in_playlists", false, false },
+ { "decoder", true, true },
+ { "input", true, true },
+ { "gapless_mp3_playback", false, false },
+ { "playlist_plugin", true, true },
+ { "auto_update", false, false },
+ { "auto_update_depth", false, false },
+ { "despotify_user", false, false },
+ { "despotify_password", false, false},
+ { "despotify_high_bitrate", false, false },
+ { "filter", true, true },
+ { "database", false, true },
+};
+
+static constexpr unsigned n_config_templates =
+ sizeof(config_templates) / sizeof(config_templates[0]);
+
+static_assert(n_config_templates == unsigned(CONF_MAX),
+ "Wrong number of config_templates");
+
+ConfigOption
+ParseConfigOptionName(const char *name)
+{
+ for (unsigned i = 0; i < n_config_templates; ++i)
+ if (strcmp(config_templates[i].name, name) == 0)
+ return ConfigOption(i);
+
+ return CONF_MAX;
+}
diff --git a/src/ConfigTemplates.hxx b/src/ConfigTemplates.hxx
new file mode 100644
index 000000000..4f5460460
--- /dev/null
+++ b/src/ConfigTemplates.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_CONFIG_TEMPLATES_HXX
+#define MPD_CONFIG_TEMPLATES_HXX
+
+#include "ConfigOption.hxx"
+
+struct ConfigTemplate {
+ const char *const name;
+ const bool repeatable;
+ const bool block;
+};
+
+extern const ConfigTemplate config_templates[];
+
+#endif
diff --git a/src/CrossFade.cxx b/src/CrossFade.cxx
new file mode 100644
index 000000000..4f5ff32ca
--- /dev/null
+++ b/src/CrossFade.cxx
@@ -0,0 +1,138 @@
+/*
+ * 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 "CrossFade.hxx"
+#include "MusicChunk.hxx"
+#include "AudioFormat.hxx"
+#include "Tag.hxx"
+
+#include <cmath>
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+#include <glib.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "crossfade"
+
+#ifdef G_OS_WIN32
+static char *
+strtok_r(char *str, const char *delim, G_GNUC_UNUSED char **saveptr)
+{
+ return strtok(str, delim);
+}
+#endif
+
+static float mixramp_interpolate(char *ramp_list, float required_db)
+{
+ float db, secs, last_db = nan(""), last_secs = 0;
+ char *ramp_str, *save_str = NULL;
+
+ /* ramp_list is a string of pairs of dBs and seconds that describe the
+ * volume profile. Delimiters are semi-colons between pairs and spaces
+ * between the dB and seconds of a pair.
+ * The dB values must be monotonically increasing for this to work. */
+
+ while (1) {
+ /* Parse the dB tokens out of the input string. */
+ ramp_str = strtok_r(ramp_list, " ", &save_str);
+
+ /* Tell strtok to continue next time round. */
+ ramp_list = NULL;
+
+ /* Parse the dB value. */
+ if (NULL == ramp_str) {
+ return nan("");
+ }
+ db = (float)atof(ramp_str);
+
+ /* Parse the time. */
+ ramp_str = strtok_r(NULL, ";", &save_str);
+ if (NULL == ramp_str) {
+ return nan("");
+ }
+ secs = (float)atof(ramp_str);
+
+ /* Check for exact match. */
+ if (db == required_db) {
+ return secs;
+ }
+
+ /* Save if too quiet. */
+ if (db < required_db) {
+ last_db = db;
+ last_secs = secs;
+ continue;
+ }
+
+ /* If required db < any stored value, use the least. */
+ if (std::isnan(last_db))
+ return secs;
+
+ /* Finally, interpolate linearly. */
+ secs = last_secs + (required_db - last_db) * (secs - last_secs) / (db - last_db);
+ return secs;
+ }
+}
+
+unsigned cross_fade_calc(float duration, float total_time,
+ float mixramp_db, float mixramp_delay,
+ float replay_gain_db, float replay_gain_prev_db,
+ char *mixramp_start, char *mixramp_prev_end,
+ const AudioFormat af,
+ const AudioFormat old_format,
+ unsigned max_chunks)
+{
+ unsigned int chunks = 0;
+ float chunks_f;
+ float mixramp_overlap;
+
+ if (duration < 0 || duration >= total_time ||
+ /* we can't crossfade when the audio formats are different */
+ af != old_format)
+ return 0;
+
+ assert(duration >= 0);
+ assert(af.IsValid());
+
+ chunks_f = (float)af.GetTimeToSize() / (float)CHUNK_SIZE;
+
+ if (std::isnan(mixramp_delay) || !mixramp_start || !mixramp_prev_end) {
+ chunks = (chunks_f * duration + 0.5);
+ } else {
+ /* Calculate mixramp overlap. */
+ mixramp_overlap = mixramp_interpolate(mixramp_start, mixramp_db - replay_gain_db)
+ + mixramp_interpolate(mixramp_prev_end, mixramp_db - replay_gain_prev_db);
+ if (!std::isnan(mixramp_overlap) &&
+ mixramp_delay <= mixramp_overlap) {
+ chunks = (chunks_f * (mixramp_overlap - mixramp_delay));
+ g_debug("will overlap %d chunks, %fs", chunks,
+ mixramp_overlap - mixramp_delay);
+ }
+ }
+
+ if (chunks > max_chunks) {
+ chunks = max_chunks;
+ g_warning("audio_buffer_size too small for computed MixRamp overlap");
+ }
+
+ return chunks;
+}
diff --git a/src/CrossFade.hxx b/src/CrossFade.hxx
new file mode 100644
index 000000000..f285c0e9a
--- /dev/null
+++ b/src/CrossFade.hxx
@@ -0,0 +1,50 @@
+/*
+ * 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_CROSSFADE_HXX
+#define MPD_CROSSFADE_HXX
+
+struct AudioFormat;
+struct music_chunk;
+
+/**
+ * Calculate how many music pipe chunks should be used for crossfading.
+ *
+ * @param duration the requested crossfade duration
+ * @param total_time total_time the duration of the new song
+ * @param mixramp_db the current mixramp_db setting
+ * @param mixramp_delay the current mixramp_delay setting
+ * @param replay_gain_db the ReplayGain adjustment used for this song
+ * @param replay_gain_prev_db the ReplayGain adjustment used on the last song
+ * @param mixramp_start the next songs mixramp_start tag
+ * @param mixramp_prev_end the last songs mixramp_end setting
+ * @param af the audio format of the new song
+ * @param old_format the audio format of the current song
+ * @param max_chunks the maximum number of chunks
+ * @return the number of chunks for crossfading, or 0 if cross fading
+ * should be disabled for this song change
+ */
+unsigned cross_fade_calc(float duration, float total_time,
+ float mixramp_db, float mixramp_delay,
+ float replay_gain_db, float replay_gain_prev_db,
+ char *mixramp_start, char *mixramp_prev_end,
+ AudioFormat af, AudioFormat old_format,
+ unsigned max_chunks);
+
+#endif
diff --git a/src/DatabaseCommands.cxx b/src/DatabaseCommands.cxx
new file mode 100644
index 000000000..42c7d4c32
--- /dev/null
+++ b/src/DatabaseCommands.cxx
@@ -0,0 +1,220 @@
+/*
+ * 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 "DatabaseCommands.hxx"
+#include "DatabaseQueue.hxx"
+#include "DatabasePlaylist.hxx"
+#include "DatabasePrint.hxx"
+#include "DatabaseSelection.hxx"
+#include "CommandError.hxx"
+#include "ClientInternal.hxx"
+#include "Tag.hxx"
+#include "util/UriUtil.hxx"
+#include "SongFilter.hxx"
+#include "protocol/Result.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+enum command_return
+handle_lsinfo2(Client *client, int argc, char *argv[])
+{
+ const char *uri;
+
+ if (argc == 2)
+ uri = argv[1];
+ else
+ /* default is root directory */
+ uri = "";
+
+ const DatabaseSelection selection(uri, false);
+
+ GError *error = NULL;
+ if (!db_selection_print(client, selection, true, &error))
+ return print_error(client, error);
+
+ return COMMAND_RETURN_OK;
+}
+
+static enum command_return
+handle_match(Client *client, int argc, char *argv[], bool fold_case)
+{
+ SongFilter filter;
+ if (!filter.Parse(argc - 1, argv + 1, fold_case)) {
+ command_error(client, ACK_ERROR_ARG, "incorrect arguments");
+ return COMMAND_RETURN_ERROR;
+ }
+
+ const DatabaseSelection selection("", true, &filter);
+
+ GError *error = NULL;
+ return db_selection_print(client, selection, true, &error)
+ ? COMMAND_RETURN_OK
+ : print_error(client, error);
+}
+
+enum command_return
+handle_find(Client *client, int argc, char *argv[])
+{
+ return handle_match(client, argc, argv, false);
+}
+
+enum command_return
+handle_search(Client *client, int argc, char *argv[])
+{
+ return handle_match(client, argc, argv, true);
+}
+
+static enum command_return
+handle_match_add(Client *client, int argc, char *argv[], bool fold_case)
+{
+ SongFilter filter;
+ if (!filter.Parse(argc - 1, argv + 1, fold_case)) {
+ command_error(client, ACK_ERROR_ARG, "incorrect arguments");
+ return COMMAND_RETURN_ERROR;
+ }
+
+ const DatabaseSelection selection("", true, &filter);
+ GError *error = NULL;
+ return AddFromDatabase(client->partition, selection, &error)
+ ? COMMAND_RETURN_OK
+ : print_error(client, error);
+}
+
+enum command_return
+handle_findadd(Client *client, int argc, char *argv[])
+{
+ return handle_match_add(client, argc, argv, false);
+}
+
+enum command_return
+handle_searchadd(Client *client, int argc, char *argv[])
+{
+ return handle_match_add(client, argc, argv, true);
+}
+
+enum command_return
+handle_searchaddpl(Client *client, int argc, char *argv[])
+{
+ const char *playlist = argv[1];
+
+ SongFilter filter;
+ if (!filter.Parse(argc - 2, argv + 2, true)) {
+ command_error(client, ACK_ERROR_ARG, "incorrect arguments");
+ return COMMAND_RETURN_ERROR;
+ }
+
+ GError *error = NULL;
+ return search_add_to_playlist("", playlist, &filter, &error)
+ ? COMMAND_RETURN_OK
+ : print_error(client, error);
+}
+
+enum command_return
+handle_count(Client *client, int argc, char *argv[])
+{
+ SongFilter filter;
+ if (!filter.Parse(argc - 1, argv + 1, false)) {
+ command_error(client, ACK_ERROR_ARG, "incorrect arguments");
+ return COMMAND_RETURN_ERROR;
+ }
+
+ GError *error = NULL;
+ return searchStatsForSongsIn(client, "", &filter, &error)
+ ? COMMAND_RETURN_OK
+ : print_error(client, error);
+}
+
+enum command_return
+handle_listall(Client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ const char *directory = "";
+
+ if (argc == 2)
+ directory = argv[1];
+
+ GError *error = NULL;
+ return printAllIn(client, directory, &error)
+ ? COMMAND_RETURN_OK
+ : print_error(client, error);
+}
+
+enum command_return
+handle_list(Client *client, int argc, char *argv[])
+{
+ unsigned tagType = locate_parse_type(argv[1]);
+
+ if (tagType == TAG_NUM_OF_ITEM_TYPES) {
+ command_error(client, ACK_ERROR_ARG, "\"%s\" is not known", argv[1]);
+ return COMMAND_RETURN_ERROR;
+ }
+
+ if (tagType == LOCATE_TAG_ANY_TYPE) {
+ command_error(client, ACK_ERROR_ARG,
+ "\"any\" is not a valid return tag type");
+ return COMMAND_RETURN_ERROR;
+ }
+
+ /* for compatibility with < 0.12.0 */
+ SongFilter *filter;
+ if (argc == 3) {
+ if (tagType != TAG_ALBUM) {
+ command_error(client, ACK_ERROR_ARG,
+ "should be \"%s\" for 3 arguments",
+ tag_item_names[TAG_ALBUM]);
+ return COMMAND_RETURN_ERROR;
+ }
+
+ filter = new SongFilter((unsigned)TAG_ARTIST, argv[2]);
+ } else if (argc > 2) {
+ filter = new SongFilter();
+ if (!filter->Parse(argc - 2, argv + 2, false)) {
+ delete filter;
+ command_error(client, ACK_ERROR_ARG,
+ "not able to parse args");
+ return COMMAND_RETURN_ERROR;
+ }
+ } else
+ filter = nullptr;
+
+ GError *error = NULL;
+ enum command_return ret =
+ listAllUniqueTags(client, tagType, filter, &error)
+ ? COMMAND_RETURN_OK
+ : print_error(client, error);
+
+ delete filter;
+
+ return ret;
+}
+
+enum command_return
+handle_listallinfo(Client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ const char *directory = "";
+
+ if (argc == 2)
+ directory = argv[1];
+
+ GError *error = NULL;
+ return printInfoForAllIn(client, directory, &error)
+ ? COMMAND_RETURN_OK
+ : print_error(client, error);
+}
diff --git a/src/DatabaseCommands.hxx b/src/DatabaseCommands.hxx
new file mode 100644
index 000000000..335adc4d6
--- /dev/null
+++ b/src/DatabaseCommands.hxx
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ */
+
+#ifndef MPD_DATABASE_COMMANDS_HXX
+#define MPD_DATABASE_COMMANDS_HXX
+
+#include "command.h"
+
+class Client;
+
+enum command_return
+handle_lsinfo2(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_find(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_findadd(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_search(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_searchadd(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_searchaddpl(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_count(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_listall(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_list(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_listallinfo(Client *client, int argc, char *argv[]);
+
+#endif
diff --git a/src/DatabaseGlue.cxx b/src/DatabaseGlue.cxx
new file mode 100644
index 000000000..e2076be28
--- /dev/null
+++ b/src/DatabaseGlue.cxx
@@ -0,0 +1,167 @@
+/*
+ * 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.
+ */
+
+#include "config.h"
+#include "DatabaseGlue.hxx"
+#include "DatabaseSimple.hxx"
+#include "DatabaseRegistry.hxx"
+#include "DatabaseSave.hxx"
+#include "Directory.hxx"
+#include "conf.h"
+
+extern "C" {
+#include "db_error.h"
+#include "stats.h"
+}
+
+#include "DatabasePlugin.hxx"
+#include "db/SimpleDatabasePlugin.hxx"
+
+#include <glib.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <assert.h>
+#include <string.h>
+#include <errno.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "database"
+
+static Database *db;
+static bool db_is_open;
+static bool is_simple;
+
+bool
+DatabaseGlobalInit(const config_param &param, GError **error_r)
+{
+ assert(db == NULL);
+ assert(!db_is_open);
+
+ const char *plugin_name =
+ param.GetBlockValue("plugin", "simple");
+ is_simple = strcmp(plugin_name, "simple") == 0;
+
+ const DatabasePlugin *plugin = GetDatabasePluginByName(plugin_name);
+ if (plugin == NULL) {
+ g_set_error(error_r, db_quark(), 0,
+ "No such database plugin: %s", plugin_name);
+ return false;
+ }
+
+ db = plugin->create(param, error_r);
+ return db != NULL;
+}
+
+void
+DatabaseGlobalDeinit(void)
+{
+ if (db_is_open)
+ db->Close();
+
+ if (db != NULL)
+ delete db;
+}
+
+const Database *
+GetDatabase()
+{
+ assert(db == NULL || db_is_open);
+
+ return db;
+}
+
+const Database *
+GetDatabase(GError **error_r)
+{
+ assert(db == nullptr || db_is_open);
+
+ if (db == nullptr)
+ g_set_error_literal(error_r, db_quark(), DB_DISABLED,
+ "No database");
+
+ return db;
+}
+
+bool
+db_is_simple(void)
+{
+ assert(db == NULL || db_is_open);
+
+ return is_simple;
+}
+
+Directory *
+db_get_root(void)
+{
+ assert(db != NULL);
+ assert(db_is_simple());
+
+ return ((SimpleDatabase *)db)->GetRoot();
+}
+
+Directory *
+db_get_directory(const char *name)
+{
+ if (db == NULL)
+ return NULL;
+
+ Directory *music_root = db_get_root();
+ if (name == NULL)
+ return music_root;
+
+ return music_root->LookupDirectory(name);
+}
+
+bool
+db_save(GError **error_r)
+{
+ assert(db != NULL);
+ assert(db_is_open);
+ assert(db_is_simple());
+
+ return ((SimpleDatabase *)db)->Save(error_r);
+}
+
+bool
+DatabaseGlobalOpen(GError **error)
+{
+ assert(db != NULL);
+ assert(!db_is_open);
+
+ if (!db->Open(error))
+ return false;
+
+ db_is_open = true;
+
+ stats_update();
+
+ return true;
+}
+
+time_t
+db_get_mtime(void)
+{
+ assert(db != NULL);
+ assert(db_is_open);
+ assert(db_is_simple());
+
+ return ((SimpleDatabase *)db)->GetLastModified();
+}
diff --git a/src/DatabaseGlue.hxx b/src/DatabaseGlue.hxx
new file mode 100644
index 000000000..e321e0ba8
--- /dev/null
+++ b/src/DatabaseGlue.hxx
@@ -0,0 +1,59 @@
+/*
+ * 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_DATABASE_GLUE_HXX
+#define MPD_DATABASE_GLUE_HXX
+
+#include "gcc.h"
+#include "gerror.h"
+
+struct config_param;
+class Database;
+
+/**
+ * Initialize the database library.
+ *
+ * @param param the database configuration block
+ */
+bool
+DatabaseGlobalInit(const config_param &param, GError **error_r);
+
+void
+DatabaseGlobalDeinit(void);
+
+bool
+DatabaseGlobalOpen(GError **error);
+
+/**
+ * Returns the global #Database instance. May return NULL if this MPD
+ * configuration has no database (no music_directory was configured).
+ */
+gcc_pure
+const Database *
+GetDatabase();
+
+/**
+ * Returns the global #Database instance. May return NULL if this MPD
+ * configuration has no database (no music_directory was configured).
+ */
+gcc_pure
+const Database *
+GetDatabase(GError **error_r);
+
+#endif
diff --git a/src/DatabaseHelpers.cxx b/src/DatabaseHelpers.cxx
new file mode 100644
index 000000000..ecaf44915
--- /dev/null
+++ b/src/DatabaseHelpers.cxx
@@ -0,0 +1,134 @@
+/*
+ * 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 "DatabaseHelpers.hxx"
+#include "DatabasePlugin.hxx"
+#include "Song.hxx"
+#include "Tag.hxx"
+
+#include <functional>
+#include <set>
+
+#include <string.h>
+
+struct StringLess {
+ gcc_pure
+ bool operator()(const char *a, const char *b) const {
+ return strcmp(a, b) < 0;
+ }
+};
+
+typedef std::set<const char *, StringLess> StringSet;
+
+static bool
+CollectTags(StringSet &set, enum tag_type tag_type, Song &song)
+{
+ Tag *tag = song.tag;
+ if (tag == nullptr)
+ return true;
+
+ bool found = false;
+ for (unsigned i = 0; i < tag->num_items; ++i) {
+ if (tag->items[i]->type == tag_type) {
+ set.insert(tag->items[i]->value);
+ found = true;
+ }
+ }
+
+ if (!found)
+ set.insert("");
+
+ return true;
+}
+
+bool
+VisitUniqueTags(const Database &db, const DatabaseSelection &selection,
+ enum tag_type tag_type,
+ VisitString visit_string,
+ GError **error_r)
+{
+ StringSet set;
+
+ using namespace std::placeholders;
+ const auto f = std::bind(CollectTags, std::ref(set), tag_type, _1);
+ if (!db.Visit(selection, f, error_r))
+ return false;
+
+ for (auto value : set)
+ if (!visit_string(value, error_r))
+ return false;
+
+ return true;
+}
+
+static void
+StatsVisitTag(DatabaseStats &stats, StringSet &artists, StringSet &albums,
+ const Tag &tag)
+{
+ if (tag.time > 0)
+ stats.total_duration += tag.time;
+
+ for (unsigned i = 0; i < tag.num_items; ++i) {
+ const TagItem &item = *tag.items[i];
+
+ switch (item.type) {
+ case TAG_ARTIST:
+ artists.insert(item.value);
+ break;
+
+ case TAG_ALBUM:
+ albums.insert(item.value);
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+static bool
+StatsVisitSong(DatabaseStats &stats, StringSet &artists, StringSet &albums,
+ Song &song)
+{
+ ++stats.song_count;
+
+ if (song.tag != nullptr)
+ StatsVisitTag(stats, artists, albums, *song.tag);
+
+ return true;
+}
+
+bool
+GetStats(const Database &db, const DatabaseSelection &selection,
+ DatabaseStats &stats, GError **error_r)
+{
+ stats.Clear();
+
+ StringSet artists, albums;
+ using namespace std::placeholders;
+ const auto f = std::bind(StatsVisitSong,
+ std::ref(stats), std::ref(artists),
+ std::ref(albums), _1);
+ if (!db.Visit(selection, f, error_r))
+ return false;
+
+ stats.artist_count = artists.size();
+ stats.album_count = albums.size();
+ return true;
+}
diff --git a/src/DatabaseHelpers.hxx b/src/DatabaseHelpers.hxx
new file mode 100644
index 000000000..5b71fd2fb
--- /dev/null
+++ b/src/DatabaseHelpers.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_MEMORY_DATABASE_PLUGIN_HXX
+#define MPD_MEMORY_DATABASE_PLUGIN_HXX
+
+#include "DatabaseVisitor.hxx"
+#include "TagType.h"
+#include "gcc.h"
+
+class Database;
+struct DatabaseSelection;
+struct DatabaseStats;
+
+bool
+VisitUniqueTags(const Database &db, const DatabaseSelection &selection,
+ enum tag_type tag_type,
+ VisitString visit_string,
+ GError **error_r);
+
+bool
+GetStats(const Database &db, const DatabaseSelection &selection,
+ DatabaseStats &stats, GError **error_r);
+
+#endif
diff --git a/src/DatabaseLock.cxx b/src/DatabaseLock.cxx
new file mode 100644
index 000000000..398e5aebb
--- /dev/null
+++ b/src/DatabaseLock.cxx
@@ -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.
+ */
+
+#include "config.h"
+#include "DatabaseLock.hxx"
+#include "gcc.h"
+
+Mutex db_mutex;
+
+#ifndef NDEBUG
+GThread *db_mutex_holder;
+#endif
diff --git a/src/DatabaseLock.hxx b/src/DatabaseLock.hxx
new file mode 100644
index 000000000..371a7d7b2
--- /dev/null
+++ b/src/DatabaseLock.hxx
@@ -0,0 +1,99 @@
+/*
+ * 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
+ *
+ * Support for locking data structures from the database, for safe
+ * multi-threading.
+ */
+
+#ifndef MPD_DB_LOCK_HXX
+#define MPD_DB_LOCK_HXX
+
+#include "check.h"
+#include "thread/Mutex.hxx"
+
+#include <glib.h>
+#include <assert.h>
+
+extern Mutex db_mutex;
+
+#ifndef NDEBUG
+
+extern GThread *db_mutex_holder;
+
+/**
+ * Does the current thread hold the database lock?
+ */
+G_GNUC_PURE
+static inline bool
+holding_db_lock(void)
+{
+ return db_mutex_holder == g_thread_self();
+}
+
+#endif
+
+/**
+ * Obtain the global database lock. This is needed before
+ * dereferencing a #song or #directory. It is not recursive.
+ */
+static inline void
+db_lock(void)
+{
+ assert(!holding_db_lock());
+
+ db_mutex.lock();
+
+ assert(db_mutex_holder == NULL);
+#ifndef NDEBUG
+ db_mutex_holder = g_thread_self();
+#endif
+}
+
+/**
+ * Release the global database lock.
+ */
+static inline void
+db_unlock(void)
+{
+ assert(holding_db_lock());
+#ifndef NDEBUG
+ db_mutex_holder = NULL;
+#endif
+
+ db_mutex.unlock();
+}
+
+#ifdef __cplusplus
+
+class ScopeDatabaseLock {
+public:
+ ScopeDatabaseLock() {
+ db_lock();
+ }
+
+ ~ScopeDatabaseLock() {
+ db_unlock();
+ }
+};
+
+#endif
+
+#endif
diff --git a/src/DatabasePlaylist.cxx b/src/DatabasePlaylist.cxx
new file mode 100644
index 000000000..04d1a490d
--- /dev/null
+++ b/src/DatabasePlaylist.cxx
@@ -0,0 +1,50 @@
+/*
+ * 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 "DatabasePlaylist.hxx"
+#include "DatabaseSelection.hxx"
+#include "PlaylistFile.hxx"
+#include "DatabaseGlue.hxx"
+#include "DatabasePlugin.hxx"
+
+#include <functional>
+
+static bool
+AddSong(const char *playlist_path_utf8,
+ Song &song, GError **error_r)
+{
+ return spl_append_song(playlist_path_utf8, &song, error_r);
+}
+
+bool
+search_add_to_playlist(const char *uri, const char *playlist_path_utf8,
+ const SongFilter *filter,
+ GError **error_r)
+{
+ const Database *db = GetDatabase(error_r);
+ if (db == nullptr)
+ return false;
+
+ const DatabaseSelection selection(uri, true, filter);
+
+ using namespace std::placeholders;
+ const auto f = std::bind(AddSong, playlist_path_utf8, _1, _2);
+ return db->Visit(selection, f, error_r);
+}
diff --git a/src/DatabasePlaylist.hxx b/src/DatabasePlaylist.hxx
new file mode 100644
index 000000000..7c6952ffa
--- /dev/null
+++ b/src/DatabasePlaylist.hxx
@@ -0,0 +1,34 @@
+/*
+ * 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_DATABASE_PLAYLIST_HXX
+#define MPD_DATABASE_PLAYLIST_HXX
+
+#include "gcc.h"
+#include "gerror.h"
+
+class SongFilter;
+
+gcc_nonnull(1,2)
+bool
+search_add_to_playlist(const char *uri, const char *path_utf8,
+ const SongFilter *filter,
+ GError **error_r);
+
+#endif
diff --git a/src/DatabasePlugin.hxx b/src/DatabasePlugin.hxx
new file mode 100644
index 000000000..835244020
--- /dev/null
+++ b/src/DatabasePlugin.hxx
@@ -0,0 +1,146 @@
+/*
+ * 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.
+ */
+
+/** \file
+ *
+ * This header declares the db_plugin class. It describes a
+ * plugin API for databases of song metadata.
+ */
+
+#ifndef MPD_DATABASE_PLUGIN_HXX
+#define MPD_DATABASE_PLUGIN_HXX
+
+#include "DatabaseVisitor.hxx"
+#include "TagType.h"
+#include "gcc.h"
+
+struct config_param;
+struct DatabaseSelection;
+struct db_visitor;
+struct Song;
+
+struct DatabaseStats {
+ /**
+ * Number of songs.
+ */
+ unsigned song_count;
+
+ /**
+ * Total duration of all songs (in seconds).
+ */
+ unsigned long total_duration;
+
+ /**
+ * Number of distinct artist names.
+ */
+ unsigned artist_count;
+
+ /**
+ * Number of distinct album names.
+ */
+ unsigned album_count;
+
+ void Clear() {
+ song_count = 0;
+ total_duration = 0;
+ artist_count = album_count = 0;
+ }
+};
+
+class Database {
+public:
+ /**
+ * Free instance data.
+ */
+ virtual ~Database() {}
+
+ /**
+ * Open the database. Read it into memory if applicable.
+ */
+ virtual bool Open(gcc_unused GError **error_r) {
+ return true;
+ }
+
+ /**
+ * Close the database, free allocated memory.
+ */
+ virtual void Close() {}
+
+ /**
+ * Look up a song (including tag data) in the database. When
+ * you don't need this anymore, call ReturnSong().
+ *
+ * @param uri_utf8 the URI of the song within the music
+ * directory (UTF-8)
+ */
+ virtual Song *GetSong(const char *uri_utf8,
+ GError **error_r) const = 0;
+
+ /**
+ * Mark the song object as "unused". Call this on objects
+ * returned by GetSong().
+ */
+ virtual void ReturnSong(Song *song) const = 0;
+
+ /**
+ * Visit the selected entities.
+ */
+ virtual bool Visit(const DatabaseSelection &selection,
+ VisitDirectory visit_directory,
+ VisitSong visit_song,
+ VisitPlaylist visit_playlist,
+ GError **error_r) const = 0;
+
+ bool Visit(const DatabaseSelection &selection,
+ VisitDirectory visit_directory,
+ VisitSong visit_song,
+ GError **error_r) const {
+ return Visit(selection, visit_directory, visit_song,
+ VisitPlaylist(), error_r);
+ }
+
+ bool Visit(const DatabaseSelection &selection, VisitSong visit_song,
+ GError **error_r) const {
+ return Visit(selection, VisitDirectory(), visit_song, error_r);
+ }
+
+ /**
+ * Visit all unique tag values.
+ */
+ virtual bool VisitUniqueTags(const DatabaseSelection &selection,
+ enum tag_type tag_type,
+ VisitString visit_string,
+ GError **error_r) const = 0;
+
+ virtual bool GetStats(const DatabaseSelection &selection,
+ DatabaseStats &stats,
+ GError **error_r) const = 0;
+};
+
+struct DatabasePlugin {
+ const char *name;
+
+ /**
+ * Allocates and configures a database.
+ */
+ Database *(*create)(const config_param &param,
+ GError **error_r);
+};
+
+#endif
diff --git a/src/DatabasePrint.cxx b/src/DatabasePrint.cxx
new file mode 100644
index 000000000..093b0bd46
--- /dev/null
+++ b/src/DatabasePrint.cxx
@@ -0,0 +1,227 @@
+/*
+ * 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 "DatabasePrint.hxx"
+#include "DatabaseSelection.hxx"
+#include "SongFilter.hxx"
+#include "PlaylistVector.hxx"
+#include "SongPrint.hxx"
+#include "TimePrint.hxx"
+#include "Directory.hxx"
+#include "Client.hxx"
+#include "Tag.hxx"
+#include "Song.hxx"
+#include "DatabaseGlue.hxx"
+#include "DatabasePlugin.hxx"
+
+#include <functional>
+
+static bool
+PrintDirectory(Client *client, const Directory &directory)
+{
+ if (!directory.IsRoot())
+ client_printf(client, "directory: %s\n", directory.GetPath());
+
+ return true;
+}
+
+static void
+print_playlist_in_directory(Client *client,
+ const Directory &directory,
+ const char *name_utf8)
+{
+ if (directory.IsRoot())
+ client_printf(client, "playlist: %s\n", name_utf8);
+ else
+ client_printf(client, "playlist: %s/%s\n",
+ directory.GetPath(), name_utf8);
+}
+
+static bool
+PrintSongBrief(Client *client, Song &song)
+{
+ assert(song.parent != NULL);
+
+ song_print_uri(client, &song);
+
+ if (song.tag != NULL && song.tag->has_playlist)
+ /* this song file has an embedded CUE sheet */
+ print_playlist_in_directory(client, *song.parent, song.uri);
+
+ return true;
+}
+
+static bool
+PrintSongFull(Client *client, Song &song)
+{
+ assert(song.parent != NULL);
+
+ song_print_info(client, &song);
+
+ if (song.tag != NULL && song.tag->has_playlist)
+ /* this song file has an embedded CUE sheet */
+ print_playlist_in_directory(client, *song.parent, song.uri);
+
+ return true;
+}
+
+static bool
+PrintPlaylistBrief(Client *client,
+ const PlaylistInfo &playlist,
+ const Directory &directory)
+{
+ print_playlist_in_directory(client, directory, playlist.name.c_str());
+ return true;
+}
+
+static bool
+PrintPlaylistFull(Client *client,
+ const PlaylistInfo &playlist,
+ const Directory &directory)
+{
+ print_playlist_in_directory(client, directory, playlist.name.c_str());
+
+ if (playlist.mtime > 0)
+ time_print(client, "Last-Modified", playlist.mtime);
+
+ return true;
+}
+
+bool
+db_selection_print(Client *client, const DatabaseSelection &selection,
+ bool full, GError **error_r)
+{
+ const Database *db = GetDatabase(error_r);
+ if (db == nullptr)
+ return false;
+
+ using namespace std::placeholders;
+ const auto d = selection.filter == nullptr
+ ? std::bind(PrintDirectory, client, _1)
+ : VisitDirectory();
+ const auto s = std::bind(full ? PrintSongFull : PrintSongBrief,
+ client, _1);
+ const auto p = selection.filter == nullptr
+ ? std::bind(full ? PrintPlaylistFull : PrintPlaylistBrief,
+ client, _1, _2)
+ : VisitPlaylist();
+
+ return db->Visit(selection, d, s, p, error_r);
+}
+
+struct SearchStats {
+ int numberOfSongs;
+ unsigned long playTime;
+};
+
+static void printSearchStats(Client *client, SearchStats *stats)
+{
+ client_printf(client, "songs: %i\n", stats->numberOfSongs);
+ client_printf(client, "playtime: %li\n", stats->playTime);
+}
+
+static bool
+stats_visitor_song(SearchStats &stats, Song &song)
+{
+ stats.numberOfSongs++;
+ stats.playTime += song.GetDuration();
+
+ return true;
+}
+
+bool
+searchStatsForSongsIn(Client *client, const char *name,
+ const SongFilter *filter,
+ GError **error_r)
+{
+ const Database *db = GetDatabase(error_r);
+ if (db == nullptr)
+ return false;
+
+ const DatabaseSelection selection(name, true, filter);
+
+ SearchStats stats;
+ stats.numberOfSongs = 0;
+ stats.playTime = 0;
+
+ using namespace std::placeholders;
+ const auto f = std::bind(stats_visitor_song, std::ref(stats),
+ _1);
+ if (!db->Visit(selection, f, error_r))
+ return false;
+
+ printSearchStats(client, &stats);
+ return true;
+}
+
+bool
+printAllIn(Client *client, const char *uri_utf8, GError **error_r)
+{
+ const DatabaseSelection selection(uri_utf8, true);
+ return db_selection_print(client, selection, false, error_r);
+}
+
+bool
+printInfoForAllIn(Client *client, const char *uri_utf8,
+ GError **error_r)
+{
+ const DatabaseSelection selection(uri_utf8, true);
+ return db_selection_print(client, selection, true, error_r);
+}
+
+static bool
+PrintSongURIVisitor(Client *client, Song &song)
+{
+ song_print_uri(client, &song);
+
+ return true;
+}
+
+static bool
+PrintUniqueTag(Client *client, enum tag_type tag_type,
+ const char *value)
+{
+ client_printf(client, "%s: %s\n", tag_item_names[tag_type], value);
+ return true;
+}
+
+bool
+listAllUniqueTags(Client *client, int type,
+ const SongFilter *filter,
+ GError **error_r)
+{
+ const Database *db = GetDatabase(error_r);
+ if (db == nullptr)
+ return false;
+
+ const DatabaseSelection selection("", true, filter);
+
+ if (type == LOCATE_TAG_FILE_TYPE) {
+ using namespace std::placeholders;
+ const auto f = std::bind(PrintSongURIVisitor, client, _1);
+ return db->Visit(selection, f, error_r);
+ } else {
+ using namespace std::placeholders;
+ const auto f = std::bind(PrintUniqueTag, client,
+ (enum tag_type)type, _1);
+ return db->VisitUniqueTags(selection, (enum tag_type)type,
+ f, error_r);
+ }
+}
diff --git a/src/DatabasePrint.hxx b/src/DatabasePrint.hxx
new file mode 100644
index 000000000..68551b63c
--- /dev/null
+++ b/src/DatabasePrint.hxx
@@ -0,0 +1,57 @@
+/*
+ * 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_DB_PRINT_H
+#define MPD_DB_PRINT_H
+
+#include "gcc.h"
+#include "gerror.h"
+
+class SongFilter;
+struct DatabaseSelection;
+struct db_visitor;
+class Client;
+
+gcc_nonnull(1)
+bool
+db_selection_print(Client *client, const DatabaseSelection &selection,
+ bool full, GError **error_r);
+
+gcc_nonnull(1,2)
+bool
+printAllIn(Client *client, const char *uri_utf8, GError **error_r);
+
+gcc_nonnull(1,2)
+bool
+printInfoForAllIn(Client *client, const char *uri_utf8,
+ GError **error_r);
+
+gcc_nonnull(1,2)
+bool
+searchStatsForSongsIn(Client *client, const char *name,
+ const SongFilter *filter,
+ GError **error_r);
+
+gcc_nonnull(1)
+bool
+listAllUniqueTags(Client *client, int type,
+ const SongFilter *filter,
+ GError **error_r);
+
+#endif
diff --git a/src/DatabaseQueue.cxx b/src/DatabaseQueue.cxx
new file mode 100644
index 000000000..67031b730
--- /dev/null
+++ b/src/DatabaseQueue.cxx
@@ -0,0 +1,54 @@
+/*
+ * 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 "DatabaseQueue.hxx"
+#include "DatabaseSelection.hxx"
+#include "DatabaseGlue.hxx"
+#include "DatabasePlugin.hxx"
+#include "Partition.hxx"
+
+#include <functional>
+
+static bool
+AddToQueue(Partition &partition, Song &song, GError **error_r)
+{
+ enum playlist_result result =
+ partition.playlist.AppendSong(partition.pc, &song, NULL);
+ if (result != PLAYLIST_RESULT_SUCCESS) {
+ g_set_error(error_r, playlist_quark(), result,
+ "Playlist error");
+ return false;
+ }
+
+ return true;
+}
+
+bool
+AddFromDatabase(Partition &partition, const DatabaseSelection &selection,
+ GError **error_r)
+{
+ const Database *db = GetDatabase(error_r);
+ if (db == nullptr)
+ return false;
+
+ using namespace std::placeholders;
+ const auto f = std::bind(AddToQueue, std::ref(partition), _1, _2);
+ return db->Visit(selection, f, error_r);
+}
diff --git a/src/DatabaseQueue.hxx b/src/DatabaseQueue.hxx
new file mode 100644
index 000000000..bae5b1f05
--- /dev/null
+++ b/src/DatabaseQueue.hxx
@@ -0,0 +1,32 @@
+/*
+ * 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_DATABASE_QUEUE_HXX
+#define MPD_DATABASE_QUEUE_HXX
+
+#include "gerror.h"
+
+struct Partition;
+struct DatabaseSelection;
+
+bool
+AddFromDatabase(Partition &partition, const DatabaseSelection &selection,
+ GError **error_r);
+
+#endif
diff --git a/src/DatabaseRegistry.cxx b/src/DatabaseRegistry.cxx
new file mode 100644
index 000000000..cf01decdd
--- /dev/null
+++ b/src/DatabaseRegistry.cxx
@@ -0,0 +1,43 @@
+/*
+ * 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 "DatabaseRegistry.hxx"
+#include "db/SimpleDatabasePlugin.hxx"
+#include "db/ProxyDatabasePlugin.hxx"
+
+#include <string.h>
+
+const DatabasePlugin *const database_plugins[] = {
+ &simple_db_plugin,
+#ifdef HAVE_LIBMPDCLIENT
+ &proxy_db_plugin,
+#endif
+ NULL
+};
+
+const DatabasePlugin *
+GetDatabasePluginByName(const char *name)
+{
+ for (auto i = database_plugins; *i != nullptr; ++i)
+ if (strcmp((*i)->name, name) == 0)
+ return *i;
+
+ return nullptr;
+}
diff --git a/src/DatabaseRegistry.hxx b/src/DatabaseRegistry.hxx
new file mode 100644
index 000000000..4be581573
--- /dev/null
+++ b/src/DatabaseRegistry.hxx
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+#ifndef MPD_DATABASE_REGISTRY_HXX
+#define MPD_DATABASE_REGISTRY_HXX
+
+#include "gcc.h"
+
+struct DatabasePlugin;
+
+/**
+ * NULL terminated list of all database plugins which were enabled at
+ * compile time.
+ */
+extern const DatabasePlugin *const database_plugins[];
+
+gcc_pure
+const DatabasePlugin *
+GetDatabasePluginByName(const char *name);
+
+#endif
diff --git a/src/DatabaseSave.cxx b/src/DatabaseSave.cxx
new file mode 100644
index 000000000..56dd19129
--- /dev/null
+++ b/src/DatabaseSave.cxx
@@ -0,0 +1,173 @@
+/*
+ * 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.
+ */
+
+#include "config.h"
+#include "DatabaseSave.hxx"
+#include "DatabaseLock.hxx"
+#include "Directory.hxx"
+#include "DirectorySave.hxx"
+#include "Song.hxx"
+#include "TextFile.hxx"
+#include "TagInternal.hxx"
+#include "Tag.hxx"
+#include "fs/Path.hxx"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <stdlib.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "database"
+
+#define DIRECTORY_INFO_BEGIN "info_begin"
+#define DIRECTORY_INFO_END "info_end"
+#define DB_FORMAT_PREFIX "format: "
+#define DIRECTORY_MPD_VERSION "mpd_version: "
+#define DIRECTORY_FS_CHARSET "fs_charset: "
+#define DB_TAG_PREFIX "tag: "
+
+enum {
+ DB_FORMAT = 1,
+};
+
+G_GNUC_CONST
+static GQuark
+db_quark(void)
+{
+ return g_quark_from_static_string("database");
+}
+
+void
+db_save_internal(FILE *fp, const Directory *music_root)
+{
+ assert(music_root != NULL);
+
+ fprintf(fp, "%s\n", DIRECTORY_INFO_BEGIN);
+ fprintf(fp, DB_FORMAT_PREFIX "%u\n", DB_FORMAT);
+ fprintf(fp, "%s%s\n", DIRECTORY_MPD_VERSION, VERSION);
+ fprintf(fp, "%s%s\n", DIRECTORY_FS_CHARSET,
+ Path::GetFSCharset().c_str());
+
+ for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
+ if (!ignore_tag_items[i])
+ fprintf(fp, DB_TAG_PREFIX "%s\n", tag_item_names[i]);
+
+ fprintf(fp, "%s\n", DIRECTORY_INFO_END);
+
+ directory_save(fp, music_root);
+}
+
+bool
+db_load_internal(TextFile &file, Directory *music_root, GError **error)
+{
+ char *line;
+ int format = 0;
+ bool found_charset = false, found_version = false;
+ bool success;
+ bool tags[TAG_NUM_OF_ITEM_TYPES];
+
+ assert(music_root != NULL);
+
+ /* get initial info */
+ line = file.ReadLine();
+ if (line == NULL || strcmp(DIRECTORY_INFO_BEGIN, line) != 0) {
+ g_set_error(error, db_quark(), 0, "Database corrupted");
+ return false;
+ }
+
+ memset(tags, false, sizeof(tags));
+
+ while ((line = file.ReadLine()) != NULL &&
+ strcmp(line, DIRECTORY_INFO_END) != 0) {
+ if (g_str_has_prefix(line, DB_FORMAT_PREFIX)) {
+ format = atoi(line + sizeof(DB_FORMAT_PREFIX) - 1);
+ } else if (g_str_has_prefix(line, DIRECTORY_MPD_VERSION)) {
+ if (found_version) {
+ g_set_error(error, db_quark(), 0,
+ "Duplicate version line");
+ return false;
+ }
+
+ found_version = true;
+ } else if (g_str_has_prefix(line, DIRECTORY_FS_CHARSET)) {
+ const char *new_charset;
+
+ if (found_charset) {
+ g_set_error(error, db_quark(), 0,
+ "Duplicate charset line");
+ return false;
+ }
+
+ found_charset = true;
+
+ new_charset = line + sizeof(DIRECTORY_FS_CHARSET) - 1;
+ const std::string &old_charset = Path::GetFSCharset();
+ if (!old_charset.empty()
+ && strcmp(new_charset, old_charset.c_str())) {
+ g_set_error(error, db_quark(), 0,
+ "Existing database has charset "
+ "\"%s\" instead of \"%s\"; "
+ "discarding database file",
+ new_charset, old_charset.c_str());
+ return false;
+ }
+ } else if (g_str_has_prefix(line, DB_TAG_PREFIX)) {
+ const char *name = line + sizeof(DB_TAG_PREFIX) - 1;
+ enum tag_type tag = tag_name_parse(name);
+ if (tag == TAG_NUM_OF_ITEM_TYPES) {
+ g_set_error(error, db_quark(), 0,
+ "Unrecognized tag '%s', "
+ "discarding database file",
+ name);
+ return false;
+ }
+
+ tags[tag] = true;
+ } else {
+ g_set_error(error, db_quark(), 0,
+ "Malformed line: %s", line);
+ return false;
+ }
+ }
+
+ if (format != DB_FORMAT) {
+ g_set_error(error, db_quark(), 0,
+ "Database format mismatch, "
+ "discarding database file");
+ return false;
+ }
+
+ for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) {
+ if (!ignore_tag_items[i] && !tags[i]) {
+ g_set_error(error, db_quark(), 0,
+ "Tag list mismatch, "
+ "discarding database file");
+ return false;
+ }
+ }
+
+ g_debug("reading DB");
+
+ db_lock();
+ success = directory_load(file, music_root, error);
+ db_unlock();
+
+ return success;
+}
diff --git a/src/DatabaseSave.hxx b/src/DatabaseSave.hxx
new file mode 100644
index 000000000..40048f261
--- /dev/null
+++ b/src/DatabaseSave.hxx
@@ -0,0 +1,36 @@
+/*
+ * 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_DATABASE_SAVE_HXX
+#define MPD_DATABASE_SAVE_HXX
+
+#include "gerror.h"
+
+#include <stdio.h>
+
+struct Directory;
+class TextFile;
+
+void
+db_save_internal(FILE *file, const Directory *root);
+
+bool
+db_load_internal(TextFile &file, Directory *root, GError **error);
+
+#endif
diff --git a/src/DatabaseSelection.cxx b/src/DatabaseSelection.cxx
new file mode 100644
index 000000000..a372d5862
--- /dev/null
+++ b/src/DatabaseSelection.cxx
@@ -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.
+ */
+
+#include "DatabaseSelection.hxx"
+#include "SongFilter.hxx"
+
+bool
+DatabaseSelection::Match(const Song &song) const
+{
+ return filter == nullptr || filter->Match(song);
+}
diff --git a/src/DatabaseSelection.hxx b/src/DatabaseSelection.hxx
new file mode 100644
index 000000000..8ca04a3fc
--- /dev/null
+++ b/src/DatabaseSelection.hxx
@@ -0,0 +1,56 @@
+/*
+ * 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_DATABASE_SELECTION_HXX
+#define MPD_DATABASE_SELECTION_HXX
+
+#include "gcc.h"
+
+#include <assert.h>
+#include <stddef.h>
+
+class SongFilter;
+struct Song;
+
+struct DatabaseSelection {
+ /**
+ * The base URI of the search (UTF-8). Must not begin or end
+ * with a slash. NULL or an empty string searches the whole
+ * database.
+ */
+ const char *uri;
+
+ /**
+ * Recursively search all sub directories?
+ */
+ bool recursive;
+
+ const SongFilter *filter;
+
+ DatabaseSelection(const char *_uri, bool _recursive,
+ const SongFilter *_filter=nullptr)
+ :uri(_uri), recursive(_recursive), filter(_filter) {
+ assert(uri != NULL);
+ }
+
+ gcc_pure
+ bool Match(const Song &song) const;
+};
+
+#endif
diff --git a/src/DatabaseSimple.hxx b/src/DatabaseSimple.hxx
new file mode 100644
index 000000000..c387a64f9
--- /dev/null
+++ b/src/DatabaseSimple.hxx
@@ -0,0 +1,86 @@
+/*
+ * 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_DATABASE_SIMPLE_HXX
+#define MPD_DATABASE_SIMPLE_HXX
+
+#include "gcc.h"
+#include "gerror.h"
+
+#include <sys/time.h>
+
+struct config_param;
+struct Directory;
+struct db_selection;
+struct db_visitor;
+
+/**
+ * Check whether the default #SimpleDatabasePlugin is used. This
+ * allows using db_get_root(), db_save(), db_get_mtime() and
+ * db_exists().
+ */
+bool
+db_is_simple(void);
+
+/**
+ * Returns the root directory object. Returns NULL if there is no
+ * configured music directory.
+ *
+ * May only be used if db_is_simple() returns true.
+ */
+gcc_pure
+Directory *
+db_get_root(void);
+
+/**
+ * Caller must lock the #db_mutex.
+ */
+gcc_nonnull(1)
+gcc_pure
+Directory *
+db_get_directory(const char *name);
+
+/**
+ * May only be used if db_is_simple() returns true.
+ */
+bool
+db_save(GError **error_r);
+
+/**
+ * May only be used if db_is_simple() returns true.
+ */
+gcc_pure
+time_t
+db_get_mtime(void);
+
+/**
+ * Returns true if there is a valid database file on the disk.
+ *
+ * May only be used if db_is_simple() returns true.
+ */
+gcc_pure
+static inline bool
+db_exists(void)
+{
+ /* mtime is set only if the database file was loaded or saved
+ successfully */
+ return db_get_mtime() > 0;
+}
+
+#endif
diff --git a/src/DatabaseVisitor.hxx b/src/DatabaseVisitor.hxx
new file mode 100644
index 000000000..e36933d7a
--- /dev/null
+++ b/src/DatabaseVisitor.hxx
@@ -0,0 +1,38 @@
+/*
+ * 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_DATABASE_VISITOR_HXX
+#define MPD_DATABASE_VISITOR_HXX
+
+#include "gerror.h"
+
+#include <functional>
+
+struct Directory;
+struct Song;
+struct PlaylistInfo;
+
+typedef std::function<bool(const Directory &, GError **)> VisitDirectory;
+typedef std::function<bool(struct Song &, GError **)> VisitSong;
+typedef std::function<bool(const PlaylistInfo &, const Directory &,
+ GError **)> VisitPlaylist;
+
+typedef std::function<bool(const char *, GError **)> VisitString;
+
+#endif
diff --git a/src/DecoderAPI.cxx b/src/DecoderAPI.cxx
new file mode 100644
index 000000000..03a75b2f9
--- /dev/null
+++ b/src/DecoderAPI.cxx
@@ -0,0 +1,561 @@
+/*
+ * 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 "DecoderAPI.hxx"
+#include "AudioConfig.hxx"
+#include "replay_gain_config.h"
+#include "MusicChunk.hxx"
+#include "MusicBuffer.hxx"
+#include "MusicPipe.hxx"
+#include "DecoderControl.hxx"
+#include "DecoderInternal.hxx"
+#include "Song.hxx"
+#include "InputStream.hxx"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "decoder"
+
+void
+decoder_initialized(struct decoder *decoder,
+ const AudioFormat audio_format,
+ bool seekable, float total_time)
+{
+ struct decoder_control *dc = decoder->dc;
+ struct audio_format_string af_string;
+
+ assert(dc->state == DECODE_STATE_START);
+ assert(dc->pipe != NULL);
+ assert(decoder != NULL);
+ assert(decoder->stream_tag == NULL);
+ assert(decoder->decoder_tag == NULL);
+ assert(!decoder->seeking);
+ assert(audio_format.IsDefined());
+ assert(audio_format.IsValid());
+
+ dc->in_audio_format = audio_format;
+ dc->out_audio_format = getOutputAudioFormat(audio_format);
+
+ dc->seekable = seekable;
+ dc->total_time = total_time;
+
+ dc->Lock();
+ dc->state = DECODE_STATE_DECODE;
+ dc->client_cond.signal();
+ dc->Unlock();
+
+ g_debug("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)
+ g_debug("converting to %s",
+ audio_format_to_string(dc->out_audio_format,
+ &af_string));
+}
+
+/**
+ * Checks if we need an "initial seek". If so, then the initial seek
+ * is prepared, and the function returns true.
+ */
+G_GNUC_PURE
+static bool
+decoder_prepare_initial_seek(struct decoder *decoder)
+{
+ const struct decoder_control *dc = decoder->dc;
+ assert(dc->pipe != NULL);
+
+ if (dc->state != DECODE_STATE_DECODE)
+ /* wait until the decoder has finished initialisation
+ (reading file headers etc.) before emitting the
+ virtual "SEEK" command */
+ return false;
+
+ if (decoder->initial_seek_running)
+ /* initial seek has already begun - override any other
+ command */
+ return true;
+
+ if (decoder->initial_seek_pending) {
+ if (!dc->seekable) {
+ /* seeking is not possible */
+ decoder->initial_seek_pending = false;
+ return false;
+ }
+
+ if (dc->command == DECODE_COMMAND_NONE) {
+ /* begin initial seek */
+
+ decoder->initial_seek_pending = false;
+ decoder->initial_seek_running = true;
+ return true;
+ }
+
+ /* skip initial seek when there's another command
+ (e.g. STOP) */
+
+ decoder->initial_seek_pending = false;
+ }
+
+ return false;
+}
+
+/**
+ * Returns the current decoder command. May return a "virtual"
+ * synthesized command, e.g. to seek to the beginning of the CUE
+ * track.
+ */
+G_GNUC_PURE
+static enum decoder_command
+decoder_get_virtual_command(struct decoder *decoder)
+{
+ const struct decoder_control *dc = decoder->dc;
+ assert(dc->pipe != NULL);
+
+ if (decoder_prepare_initial_seek(decoder))
+ return DECODE_COMMAND_SEEK;
+
+ return dc->command;
+}
+
+enum decoder_command
+decoder_get_command(struct decoder *decoder)
+{
+ return decoder_get_virtual_command(decoder);
+}
+
+void
+decoder_command_finished(struct decoder *decoder)
+{
+ struct decoder_control *dc = decoder->dc;
+
+ dc->Lock();
+
+ assert(dc->command != DECODE_COMMAND_NONE ||
+ decoder->initial_seek_running);
+ assert(dc->command != DECODE_COMMAND_SEEK ||
+ decoder->initial_seek_running ||
+ dc->seek_error || decoder->seeking);
+ assert(dc->pipe != NULL);
+
+ if (decoder->initial_seek_running) {
+ assert(!decoder->seeking);
+ assert(decoder->chunk == NULL);
+ assert(music_pipe_empty(dc->pipe));
+
+ decoder->initial_seek_running = false;
+ decoder->timestamp = dc->start_ms / 1000.;
+ dc->Unlock();
+ return;
+ }
+
+ if (decoder->seeking) {
+ decoder->seeking = false;
+
+ /* delete frames from the old song position */
+
+ if (decoder->chunk != NULL) {
+ music_buffer_return(dc->buffer, decoder->chunk);
+ decoder->chunk = NULL;
+ }
+
+ music_pipe_clear(dc->pipe, dc->buffer);
+
+ decoder->timestamp = dc->seek_where;
+ }
+
+ dc->command = DECODE_COMMAND_NONE;
+ dc->client_cond.signal();
+ dc->Unlock();
+}
+
+double decoder_seek_where(G_GNUC_UNUSED struct decoder * decoder)
+{
+ const struct decoder_control *dc = decoder->dc;
+
+ assert(dc->pipe != NULL);
+
+ if (decoder->initial_seek_running)
+ return dc->start_ms / 1000.;
+
+ assert(dc->command == DECODE_COMMAND_SEEK);
+
+ decoder->seeking = true;
+
+ return dc->seek_where;
+}
+
+void decoder_seek_error(struct decoder * decoder)
+{
+ struct decoder_control *dc = decoder->dc;
+
+ assert(dc->pipe != NULL);
+
+ if (decoder->initial_seek_running) {
+ /* d'oh, we can't seek to the sub-song start position,
+ what now? - no idea, ignoring the problem for now. */
+ decoder->initial_seek_running = false;
+ return;
+ }
+
+ assert(dc->command == DECODE_COMMAND_SEEK);
+
+ dc->seek_error = true;
+ decoder->seeking = false;
+
+ decoder_command_finished(decoder);
+}
+
+/**
+ * Should be read operation be cancelled? That is the case when the
+ * player thread has sent a command such as "STOP".
+ */
+G_GNUC_PURE
+static inline bool
+decoder_check_cancel_read(const struct decoder *decoder)
+{
+ if (decoder == NULL)
+ return false;
+
+ const struct decoder_control *dc = decoder->dc;
+ if (dc->command == DECODE_COMMAND_NONE)
+ return false;
+
+ /* ignore the SEEK command during initialization, the plugin
+ should handle that after it has initialized successfully */
+ if (dc->command == DECODE_COMMAND_SEEK &&
+ (dc->state == DECODE_STATE_START || decoder->seeking))
+ return false;
+
+ return true;
+}
+
+size_t decoder_read(struct decoder *decoder,
+ struct input_stream *is,
+ void *buffer, size_t length)
+{
+ /* XXX don't allow decoder==NULL */
+ GError *error = NULL;
+ size_t nbytes;
+
+ assert(decoder == NULL ||
+ decoder->dc->state == DECODE_STATE_START ||
+ decoder->dc->state == DECODE_STATE_DECODE);
+ assert(is != NULL);
+ assert(buffer != NULL);
+
+ if (length == 0)
+ return 0;
+
+ input_stream_lock(is);
+
+ while (true) {
+ if (decoder_check_cancel_read(decoder)) {
+ input_stream_unlock(is);
+ return 0;
+ }
+
+ if (input_stream_available(is))
+ break;
+
+ is->cond.wait(is->mutex);
+ }
+
+ nbytes = input_stream_read(is, buffer, length, &error);
+ assert(nbytes == 0 || error == NULL);
+ assert(nbytes > 0 || error != NULL || input_stream_eof(is));
+
+ if (G_UNLIKELY(nbytes == 0 && error != NULL)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ }
+
+ input_stream_unlock(is);
+
+ return nbytes;
+}
+
+void
+decoder_timestamp(struct decoder *decoder, double t)
+{
+ assert(decoder != NULL);
+ assert(t >= 0);
+
+ decoder->timestamp = t;
+}
+
+/**
+ * Sends a #tag as-is to the music pipe. Flushes the current chunk
+ * (decoder.chunk) if there is one.
+ */
+static enum decoder_command
+do_send_tag(struct decoder *decoder, const Tag &tag)
+{
+ struct music_chunk *chunk;
+
+ if (decoder->chunk != NULL) {
+ /* there is a partial chunk - flush it, we want the
+ tag in a new chunk */
+ decoder_flush_chunk(decoder);
+ decoder->dc->client_cond.signal();
+ }
+
+ assert(decoder->chunk == NULL);
+
+ chunk = decoder_get_chunk(decoder);
+ if (chunk == NULL) {
+ assert(decoder->dc->command != DECODE_COMMAND_NONE);
+ return decoder->dc->command;
+ }
+
+ chunk->tag = new Tag(tag);
+ return DECODE_COMMAND_NONE;
+}
+
+static bool
+update_stream_tag(struct decoder *decoder, struct input_stream *is)
+{
+ Tag *tag;
+
+ tag = is != NULL
+ ? input_stream_lock_tag(is)
+ : NULL;
+ if (tag == NULL) {
+ tag = decoder->song_tag;
+ if (tag == NULL)
+ return false;
+
+ /* no stream tag present - submit the song tag
+ instead */
+ decoder->song_tag = NULL;
+ }
+
+ delete decoder->stream_tag;
+ decoder->stream_tag = tag;
+ return true;
+}
+
+enum decoder_command
+decoder_data(struct decoder *decoder,
+ struct input_stream *is,
+ const void *data, size_t length,
+ uint16_t kbit_rate)
+{
+ struct decoder_control *dc = decoder->dc;
+ GError *error = NULL;
+ enum decoder_command cmd;
+
+ assert(dc->state == DECODE_STATE_DECODE);
+ assert(dc->pipe != NULL);
+ assert(length % dc->in_audio_format.GetFrameSize() == 0);
+
+ dc->Lock();
+ cmd = decoder_get_virtual_command(decoder);
+ dc->Unlock();
+
+ if (cmd == DECODE_COMMAND_STOP || cmd == DECODE_COMMAND_SEEK ||
+ length == 0)
+ return cmd;
+
+ /* send stream tags */
+
+ if (update_stream_tag(decoder, is)) {
+ if (decoder->decoder_tag != NULL) {
+ /* merge with tag from decoder plugin */
+ Tag *tag = Tag::Merge(*decoder->decoder_tag,
+ *decoder->stream_tag);
+ cmd = do_send_tag(decoder, *tag);
+ delete tag;
+ } else
+ /* send only the stream tag */
+ cmd = do_send_tag(decoder, *decoder->stream_tag);
+
+ if (cmd != DECODE_COMMAND_NONE)
+ return cmd;
+ }
+
+ if (dc->in_audio_format != dc->out_audio_format) {
+ data = decoder->conv_state.Convert(dc->in_audio_format,
+ data, length,
+ dc->out_audio_format,
+ &length,
+ &error);
+ if (data == NULL) {
+ /* the PCM conversion has failed - stop
+ playback, since we have no better way to
+ bail out */
+ g_warning("%s", error->message);
+ return DECODE_COMMAND_STOP;
+ }
+ }
+
+ while (length > 0) {
+ struct music_chunk *chunk;
+ size_t nbytes;
+ bool full;
+
+ chunk = decoder_get_chunk(decoder);
+ if (chunk == NULL) {
+ assert(dc->command != DECODE_COMMAND_NONE);
+ return dc->command;
+ }
+
+ void *dest = chunk->Write(dc->out_audio_format,
+ decoder->timestamp -
+ dc->song->start_ms / 1000.0,
+ kbit_rate, &nbytes);
+ if (dest == NULL) {
+ /* the chunk is full, flush it */
+ decoder_flush_chunk(decoder);
+ dc->client_cond.signal();
+ continue;
+ }
+
+ assert(nbytes > 0);
+
+ if (nbytes > length)
+ nbytes = length;
+
+ /* copy the buffer */
+
+ memcpy(dest, data, nbytes);
+
+ /* expand the music pipe chunk */
+
+ full = chunk->Expand(dc->out_audio_format, nbytes);
+ if (full) {
+ /* the chunk is full, flush it */
+ decoder_flush_chunk(decoder);
+ dc->client_cond.signal();
+ }
+
+ data = (const uint8_t *)data + nbytes;
+ length -= nbytes;
+
+ decoder->timestamp += (double)nbytes /
+ dc->out_audio_format.GetTimeToSize();
+
+ if (dc->end_ms > 0 &&
+ decoder->timestamp >= dc->end_ms / 1000.0)
+ /* the end of this range has been reached:
+ stop decoding */
+ return DECODE_COMMAND_STOP;
+ }
+
+ return DECODE_COMMAND_NONE;
+}
+
+enum decoder_command
+decoder_tag(G_GNUC_UNUSED struct decoder *decoder, struct input_stream *is,
+ Tag &&tag)
+{
+ G_GNUC_UNUSED const struct decoder_control *dc = decoder->dc;
+ enum decoder_command cmd;
+
+ assert(dc->state == DECODE_STATE_DECODE);
+ assert(dc->pipe != NULL);
+
+ /* save the tag */
+
+ delete decoder->decoder_tag;
+ decoder->decoder_tag = new Tag(tag);
+
+ /* check for a new stream tag */
+
+ update_stream_tag(decoder, is);
+
+ /* check if we're seeking */
+
+ if (decoder_prepare_initial_seek(decoder))
+ /* during initial seek, no music chunk must be created
+ until seeking is finished; skip the rest of the
+ function here */
+ return DECODE_COMMAND_SEEK;
+
+ /* send tag to music pipe */
+
+ if (decoder->stream_tag != NULL) {
+ /* merge with tag from input stream */
+ Tag *merged;
+
+ merged = Tag::Merge(*decoder->stream_tag,
+ *decoder->decoder_tag);
+ cmd = do_send_tag(decoder, *merged);
+ delete merged;
+ } else
+ /* send only the decoder tag */
+ cmd = do_send_tag(decoder, *decoder->decoder_tag);
+
+ return cmd;
+}
+
+void
+decoder_replay_gain(struct decoder *decoder,
+ const struct replay_gain_info *replay_gain_info)
+{
+ assert(decoder != NULL);
+
+ if (replay_gain_info != NULL) {
+ static unsigned serial;
+ if (++serial == 0)
+ serial = 1;
+
+ if (REPLAY_GAIN_OFF != replay_gain_mode) {
+ enum replay_gain_mode rgm = replay_gain_mode;
+ if (rgm != REPLAY_GAIN_ALBUM)
+ rgm = REPLAY_GAIN_TRACK;
+
+ decoder->dc->replay_gain_db = 20.0 * log10f(
+ replay_gain_tuple_scale(
+ &replay_gain_info->tuples[rgm],
+ replay_gain_preamp, replay_gain_missing_preamp,
+ replay_gain_limit));
+ }
+
+ decoder->replay_gain_info = *replay_gain_info;
+ decoder->replay_gain_serial = serial;
+
+ if (decoder->chunk != NULL) {
+ /* flush the current chunk because the new
+ replay gain values affect the following
+ samples */
+ decoder_flush_chunk(decoder);
+ decoder->dc->client_cond.signal();
+ }
+ } else
+ decoder->replay_gain_serial = 0;
+}
+
+void
+decoder_mixramp(struct decoder *decoder,
+ char *mixramp_start, char *mixramp_end)
+{
+ assert(decoder != NULL);
+ struct decoder_control *dc = decoder->dc;
+ assert(dc != NULL);
+
+ dc->MixRampStart(mixramp_start);
+ dc->MixRampEnd(mixramp_end);
+}
diff --git a/src/DecoderAPI.hxx b/src/DecoderAPI.hxx
new file mode 100644
index 000000000..a5eb2f236
--- /dev/null
+++ b/src/DecoderAPI.hxx
@@ -0,0 +1,168 @@
+/*
+ * 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
+ * \brief The MPD Decoder API
+ *
+ * This is the public API which is used by decoder plugins to
+ * communicate with the mpd core.
+ */
+
+#ifndef MPD_DECODER_API_HXX
+#define MPD_DECODER_API_HXX
+
+#include "check.h"
+#include "DecoderCommand.hxx"
+#include "DecoderPlugin.hxx"
+#include "input_stream.h"
+#include "replay_gain_info.h"
+#include "Tag.hxx"
+#include "AudioFormat.hxx"
+#include "conf.h"
+
+/**
+ * Notify the player thread that it has finished initialization and
+ * that it has read the song's meta data.
+ *
+ * @param decoder the decoder object
+ * @param audio_format the audio format which is going to be sent to
+ * decoder_data()
+ * @param seekable true if the song is seekable
+ * @param total_time the total number of seconds in this song; -1 if unknown
+ */
+void
+decoder_initialized(struct decoder *decoder,
+ AudioFormat audio_format,
+ bool seekable, float total_time);
+
+/**
+ * Determines the pending decoder command.
+ *
+ * @param decoder the decoder object
+ * @return the current command, or DECODE_COMMAND_NONE if there is no
+ * command pending
+ */
+enum decoder_command
+decoder_get_command(struct decoder *decoder);
+
+/**
+ * Called by the decoder when it has performed the requested command
+ * (dc->command). This function resets dc->command and wakes up the
+ * player thread.
+ *
+ * @param decoder the decoder object
+ */
+void
+decoder_command_finished(struct decoder *decoder);
+
+/**
+ * Call this when you have received the DECODE_COMMAND_SEEK command.
+ *
+ * @param decoder the decoder object
+ * @return the destination position for the week
+ */
+double
+decoder_seek_where(struct decoder *decoder);
+
+/**
+ * Call this instead of decoder_command_finished() when seeking has
+ * failed.
+ *
+ * @param decoder the decoder object
+ */
+void
+decoder_seek_error(struct decoder *decoder);
+
+/**
+ * Blocking read from the input stream.
+ *
+ * @param decoder the decoder object
+ * @param is the input stream to read from
+ * @param buffer the destination buffer
+ * @param length the maximum number of bytes to read
+ * @return the number of bytes read, or 0 if one of the following
+ * occurs: end of file; error; command (like SEEK or STOP).
+ */
+size_t
+decoder_read(struct decoder *decoder, struct input_stream *is,
+ void *buffer, size_t length);
+
+/**
+ * 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
+ * on the buffer size won't work.
+ */
+void
+decoder_timestamp(struct decoder *decoder, double t);
+
+/**
+ * This function is called by the decoder plugin when it has
+ * successfully decoded block of input data.
+ *
+ * @param decoder the decoder object
+ * @param is an input stream which is buffering while we are waiting
+ * for the player
+ * @param data the source buffer
+ * @param length the number of bytes in the buffer
+ * @return the current command, or DECODE_COMMAND_NONE if there is no
+ * command pending
+ */
+enum decoder_command
+decoder_data(struct decoder *decoder, struct input_stream *is,
+ const void *data, size_t length,
+ uint16_t kbit_rate);
+
+/**
+ * This function is called by the decoder plugin when it has
+ * successfully decoded a tag.
+ *
+ * @param decoder the decoder object
+ * @param is an input stream which is buffering while we are waiting
+ * for the player
+ * @param tag the tag to send
+ * @return the current command, or DECODE_COMMAND_NONE if there is no
+ * command pending
+ */
+enum decoder_command
+decoder_tag(struct decoder *decoder, struct input_stream *is, Tag &&tag);
+
+/**
+ * Set replay gain values for the following chunks.
+ *
+ * @param decoder the decoder object
+ * @param rgi the replay_gain_info object; may be NULL to invalidate
+ * the previous replay gain values
+ */
+void
+decoder_replay_gain(struct decoder *decoder,
+ const struct replay_gain_info *replay_gain_info);
+
+/**
+ * Store MixRamp tags.
+ *
+ * @param decoder the decoder object
+ * @param mixramp_start the mixramp_start tag; may be NULL to invalidate
+ * @param mixramp_end the mixramp_end tag; may be NULL to invalidate
+ */
+void
+decoder_mixramp(struct decoder *decoder,
+ char *mixramp_start, char *mixramp_end);
+
+#endif
diff --git a/src/DecoderBuffer.cxx b/src/DecoderBuffer.cxx
new file mode 100644
index 000000000..2125bbebd
--- /dev/null
+++ b/src/DecoderBuffer.cxx
@@ -0,0 +1,168 @@
+/*
+ * 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 "DecoderBuffer.hxx"
+#include "DecoderAPI.hxx"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+
+struct DecoderBuffer {
+ struct decoder *decoder;
+ struct input_stream *is;
+
+ /** the allocated size of the buffer */
+ size_t size;
+
+ /** the current length of the buffer */
+ size_t length;
+
+ /** number of bytes already consumed at the beginning of the
+ buffer */
+ size_t consumed;
+
+ /** the actual buffer (dynamic size) */
+ unsigned char data[sizeof(size_t)];
+};
+
+DecoderBuffer *
+decoder_buffer_new(struct decoder *decoder, struct input_stream *is,
+ size_t size)
+{
+ DecoderBuffer *buffer = (DecoderBuffer *)
+ g_malloc(sizeof(*buffer) - sizeof(buffer->data) + size);
+
+ assert(is != nullptr);
+ assert(size > 0);
+
+ buffer->decoder = decoder;
+ buffer->is = is;
+ buffer->size = size;
+ buffer->length = 0;
+ buffer->consumed = 0;
+
+ return buffer;
+}
+
+void
+decoder_buffer_free(DecoderBuffer *buffer)
+{
+ assert(buffer != nullptr);
+
+ g_free(buffer);
+}
+
+bool
+decoder_buffer_is_empty(const DecoderBuffer *buffer)
+{
+ return buffer->consumed == buffer->length;
+}
+
+bool
+decoder_buffer_is_full(const DecoderBuffer *buffer)
+{
+ return buffer->consumed == 0 && buffer->length == buffer->size;
+}
+
+static void
+decoder_buffer_shift(DecoderBuffer *buffer)
+{
+ assert(buffer->consumed > 0);
+
+ buffer->length -= buffer->consumed;
+ memmove(buffer->data, buffer->data + buffer->consumed, buffer->length);
+ buffer->consumed = 0;
+}
+
+bool
+decoder_buffer_fill(DecoderBuffer *buffer)
+{
+ size_t nbytes;
+
+ if (buffer->consumed > 0)
+ decoder_buffer_shift(buffer);
+
+ if (buffer->length >= buffer->size)
+ /* buffer is full */
+ return false;
+
+ nbytes = decoder_read(buffer->decoder, buffer->is,
+ buffer->data + buffer->length,
+ buffer->size - buffer->length);
+ if (nbytes == 0)
+ /* end of file, I/O error or decoder command
+ received */
+ return false;
+
+ buffer->length += nbytes;
+ assert(buffer->length <= buffer->size);
+
+ return true;
+}
+
+const void *
+decoder_buffer_read(const DecoderBuffer *buffer, size_t *length_r)
+{
+ if (buffer->consumed >= buffer->length)
+ /* buffer is empty */
+ return nullptr;
+
+ *length_r = buffer->length - buffer->consumed;
+ return buffer->data + buffer->consumed;
+}
+
+void
+decoder_buffer_consume(DecoderBuffer *buffer, size_t nbytes)
+{
+ /* just move the "consumed" pointer - decoder_buffer_shift()
+ will do the real work later (called by
+ decoder_buffer_fill()) */
+ buffer->consumed += nbytes;
+
+ assert(buffer->consumed <= buffer->length);
+}
+
+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;
+ if (nbytes == 0)
+ return true;
+ }
+
+ success = decoder_buffer_fill(buffer);
+ if (!success)
+ return false;
+ }
+}
diff --git a/src/DecoderBuffer.hxx b/src/DecoderBuffer.hxx
new file mode 100644
index 000000000..4f7efb29a
--- /dev/null
+++ b/src/DecoderBuffer.hxx
@@ -0,0 +1,105 @@
+/*
+ * 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_DECODER_BUFFER_HXX
+#define MPD_DECODER_BUFFER_HXX
+
+#include <stddef.h>
+
+/**
+ * This objects handles buffered reads in decoder plugins easily. You
+ * create a buffer object, and use its high-level methods to fill and
+ * read it. It will automatically handle shifting the buffer.
+ */
+struct DecoderBuffer;
+
+struct decoder;
+struct input_stream;
+
+/**
+ * Creates a new buffer.
+ *
+ * @param decoder the decoder object, used for decoder_read(), may be NULL
+ * @param is the input stream object where we should read from
+ * @param size the maximum size of the buffer
+ * @return the new decoder_buffer object
+ */
+DecoderBuffer *
+decoder_buffer_new(struct decoder *decoder, struct input_stream *is,
+ size_t size);
+
+/**
+ * Frees resources used by the decoder_buffer object.
+ */
+void
+decoder_buffer_free(DecoderBuffer *buffer);
+
+bool
+decoder_buffer_is_empty(const DecoderBuffer *buffer);
+
+bool
+decoder_buffer_is_full(const DecoderBuffer *buffer);
+
+/**
+ * Read data from the input_stream and append it to the buffer.
+ *
+ * @return true if data was appended; false if there is no data
+ * available (yet), end of file, I/O error or a decoder command was
+ * received
+ */
+bool
+decoder_buffer_fill(DecoderBuffer *buffer);
+
+/**
+ * Reads data from the buffer. This data is not yet consumed, you
+ * have to call decoder_buffer_consume() to do that. The returned
+ * buffer becomes invalid after a decoder_buffer_fill() or a
+ * 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 NULL if there is no data
+ * available
+ */
+const void *
+decoder_buffer_read(const DecoderBuffer *buffer, size_t *length_r);
+
+/**
+ * Consume (delete, invalidate) a part of the buffer. The "nbytes"
+ * parameter must not be larger than the length returned by
+ * decoder_buffer_read().
+ *
+ * @param buffer the decoder_buffer object
+ * @param nbytes the number of bytes to consume
+ */
+void
+decoder_buffer_consume(DecoderBuffer *buffer, size_t nbytes);
+
+/**
+ * Skips the specified number of bytes, discarding its data.
+ *
+ * @param buffer the decoder_buffer object
+ * @param nbytes the number of bytes to skip
+ * @return true on success, false on error
+ */
+bool
+decoder_buffer_skip(DecoderBuffer *buffer, size_t nbytes);
+
+#endif
diff --git a/src/DecoderCommand.hxx b/src/DecoderCommand.hxx
new file mode 100644
index 000000000..e6dc26982
--- /dev/null
+++ b/src/DecoderCommand.hxx
@@ -0,0 +1,30 @@
+/*
+ * 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_DECODER_COMMAND_HXX
+#define MPD_DECODER_COMMAND_HXX
+
+enum decoder_command {
+ DECODE_COMMAND_NONE = 0,
+ DECODE_COMMAND_START,
+ DECODE_COMMAND_STOP,
+ DECODE_COMMAND_SEEK
+};
+
+#endif
diff --git a/src/DecoderControl.cxx b/src/DecoderControl.cxx
new file mode 100644
index 000000000..6721bd55a
--- /dev/null
+++ b/src/DecoderControl.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 "DecoderControl.hxx"
+#include "MusicPipe.hxx"
+#include "Song.hxx"
+
+#include <assert.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "decoder_control"
+
+decoder_control::decoder_control()
+ :thread(nullptr),
+ state(DECODE_STATE_STOP),
+ command(DECODE_COMMAND_NONE),
+ song(nullptr),
+ replay_gain_db(0), replay_gain_prev_db(0),
+ mixramp_start(nullptr), mixramp_end(nullptr),
+ mixramp_prev_end(nullptr) {}
+
+decoder_control::~decoder_control()
+{
+ ClearError();
+
+ if (song != NULL)
+ song->Free();
+
+ g_free(mixramp_start);
+ g_free(mixramp_end);
+ g_free(mixramp_prev_end);
+}
+
+static void
+dc_command_wait_locked(struct decoder_control *dc)
+{
+ while (dc->command != DECODE_COMMAND_NONE)
+ dc->WaitForDecoder();
+}
+
+static void
+dc_command_locked(struct decoder_control *dc, enum decoder_command cmd)
+{
+ dc->command = cmd;
+ dc->Signal();
+ dc_command_wait_locked(dc);
+}
+
+static void
+dc_command(struct decoder_control *dc, enum decoder_command cmd)
+{
+ dc->Lock();
+ dc->ClearError();
+ dc_command_locked(dc, cmd);
+ dc->Unlock();
+}
+
+static void
+dc_command_async(struct decoder_control *dc, enum decoder_command cmd)
+{
+ dc->Lock();
+
+ dc->command = cmd;
+ dc->Signal();
+
+ dc->Unlock();
+}
+
+bool
+decoder_control::IsCurrentSong(const Song *_song) const
+{
+ assert(_song != NULL);
+
+ switch (state) {
+ case DECODE_STATE_STOP:
+ case DECODE_STATE_ERROR:
+ return false;
+
+ case DECODE_STATE_START:
+ case DECODE_STATE_DECODE:
+ return song_equals(song, _song);
+ }
+
+ assert(false);
+ gcc_unreachable();
+}
+
+void
+decoder_control::Start(Song *_song,
+ unsigned _start_ms, unsigned _end_ms,
+ music_buffer *_buffer, music_pipe *_pipe)
+{
+ assert(_song != NULL);
+ assert(_buffer != NULL);
+ assert(_pipe != NULL);
+ assert(music_pipe_empty(_pipe));
+
+ if (song != nullptr)
+ song->Free();
+
+ song = _song;
+ start_ms = _start_ms;
+ end_ms = _end_ms;
+ buffer = _buffer;
+ pipe = _pipe;
+
+ dc_command(this, DECODE_COMMAND_START);
+}
+
+void
+decoder_control::Stop()
+{
+ Lock();
+
+ if (command != DECODE_COMMAND_NONE)
+ /* Attempt to cancel the current command. If it's too
+ late and the decoder thread is already executing
+ the old command, we'll call STOP again in this
+ function (see below). */
+ dc_command_locked(this, DECODE_COMMAND_STOP);
+
+ if (state != DECODE_STATE_STOP && state != DECODE_STATE_ERROR)
+ dc_command_locked(this, DECODE_COMMAND_STOP);
+
+ Unlock();
+}
+
+bool
+decoder_control::Seek(double where)
+{
+ assert(state != DECODE_STATE_START);
+ assert(where >= 0.0);
+
+ if (state == DECODE_STATE_STOP ||
+ state == DECODE_STATE_ERROR || !seekable)
+ return false;
+
+ seek_where = where;
+ seek_error = false;
+ dc_command(this, DECODE_COMMAND_SEEK);
+
+ return !seek_error;
+}
+
+void
+decoder_control::Quit()
+{
+ assert(thread != nullptr);
+
+ quit = true;
+ dc_command_async(this, DECODE_COMMAND_STOP);
+
+ g_thread_join(thread);
+ thread = nullptr;
+}
+
+void
+decoder_control::MixRampStart(char *_mixramp_start)
+{
+ g_free(mixramp_start);
+ mixramp_start = _mixramp_start;
+}
+
+void
+decoder_control::MixRampEnd(char *_mixramp_end)
+{
+ g_free(mixramp_end);
+ mixramp_end = _mixramp_end;
+}
+
+void
+decoder_control::MixRampPrevEnd(char *_mixramp_prev_end)
+{
+ g_free(mixramp_prev_end);
+ mixramp_prev_end = _mixramp_prev_end;
+}
diff --git a/src/DecoderControl.hxx b/src/DecoderControl.hxx
new file mode 100644
index 000000000..31c72657b
--- /dev/null
+++ b/src/DecoderControl.hxx
@@ -0,0 +1,299 @@
+/*
+ * 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_DECODER_CONTROL_HXX
+#define MPD_DECODER_CONTROL_HXX
+
+#include "DecoderCommand.hxx"
+#include "AudioFormat.hxx"
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+
+#include <glib.h>
+
+#include <assert.h>
+
+struct Song;
+
+enum decoder_state {
+ DECODE_STATE_STOP = 0,
+ DECODE_STATE_START,
+ DECODE_STATE_DECODE,
+
+ /**
+ * The last "START" command failed, because there was an I/O
+ * error or because no decoder was able to decode the file.
+ * This state will only come after START; once the state has
+ * turned to DECODE, by definition no such error can occur.
+ */
+ DECODE_STATE_ERROR,
+};
+
+struct decoder_control {
+ /** the handle of the decoder thread, or NULL if the decoder
+ thread isn't running */
+ GThread *thread;
+
+ /**
+ * This lock protects #state and #command.
+ */
+ mutable Mutex mutex;
+
+ /**
+ * Trigger this object after you have modified #command. This
+ * is also used by the decoder thread to notify the caller
+ * when it has finished a command.
+ */
+ Cond cond;
+
+ /**
+ * The trigger of this object's client. It is signalled
+ * whenever an event occurs.
+ */
+ Cond client_cond;
+
+ enum decoder_state state;
+ enum decoder_command command;
+
+ /**
+ * The error that occurred in the decoder thread. This
+ * attribute is only valid if #state is #DECODE_STATE_ERROR.
+ * The object must be freed when this object transitions to
+ * any other state (usually #DECODE_STATE_START).
+ */
+ GError *error;
+
+ bool quit;
+ bool seek_error;
+ bool seekable;
+ double seek_where;
+
+ /** the format of the song file */
+ AudioFormat in_audio_format;
+
+ /** the format being sent to the music pipe */
+ AudioFormat out_audio_format;
+
+ /**
+ * The song currently being decoded. This attribute is set by
+ * the player thread, when it sends the #DECODE_COMMAND_START
+ * command.
+ *
+ * This is a duplicate, and must be freed when this attribute
+ * is cleared.
+ */
+ Song *song;
+
+ /**
+ * The initial seek position (in milliseconds), e.g. to the
+ * start of a sub-track described by a CUE file.
+ *
+ * This attribute is set by dc_start().
+ */
+ unsigned start_ms;
+
+ /**
+ * The decoder will stop when it reaches this position (in
+ * milliseconds). 0 means don't stop before the end of the
+ * file.
+ *
+ * This attribute is set by dc_start().
+ */
+ unsigned end_ms;
+
+ float total_time;
+
+ /** the #music_chunk allocator */
+ struct music_buffer *buffer;
+
+ /**
+ * The destination pipe for decoded chunks. The caller thread
+ * owns this object, and is responsible for freeing it.
+ */
+ struct music_pipe *pipe;
+
+ float replay_gain_db;
+ float replay_gain_prev_db;
+ char *mixramp_start;
+ char *mixramp_end;
+ char *mixramp_prev_end;
+
+ decoder_control();
+ ~decoder_control();
+
+ /**
+ * Locks the object.
+ */
+ void Lock() const {
+ mutex.lock();
+ }
+
+ /**
+ * Unlocks the object.
+ */
+ void Unlock() const {
+ mutex.unlock();
+ }
+
+ /**
+ * Signals the object. This function is only valid in the
+ * player thread. The object should be locked prior to
+ * calling this function.
+ */
+ void Signal() {
+ cond.signal();
+ }
+
+ /**
+ * Waits for a signal on the #decoder_control object. This function
+ * is only valid in the decoder thread. The object must be locked
+ * prior to calling this function.
+ */
+ void Wait() {
+ cond.wait(mutex);
+ }
+
+ /**
+ * Waits for a signal from the decoder thread. This object
+ * must be locked prior to calling this function. This method
+ * is only valid in the player thread.
+ */
+ void WaitForDecoder() {
+ client_cond.wait(mutex);
+ }
+
+ bool IsIdle() const {
+ return state == DECODE_STATE_STOP ||
+ state == DECODE_STATE_ERROR;
+ }
+
+ gcc_pure
+ bool LockIsIdle() const {
+ Lock();
+ bool result = IsIdle();
+ Unlock();
+ return result;
+ }
+
+ bool IsStarting() const {
+ return state == DECODE_STATE_START;
+ }
+
+ gcc_pure
+ bool LockIsStarting() const {
+ Lock();
+ bool result = IsStarting();
+ Unlock();
+ return result;
+ }
+
+ bool HasFailed() const {
+ assert(command == DECODE_COMMAND_NONE);
+
+ return state == DECODE_STATE_ERROR;
+ }
+
+ gcc_pure
+ bool LockHasFailed() const {
+ Lock();
+ bool result = HasFailed();
+ Unlock();
+ return result;
+ }
+
+ /**
+ * Checks whether an error has occurred, and if so, returns a newly
+ * allocated copy of the #GError object.
+ *
+ * Caller must lock the object.
+ */
+ GError *GetError() const {
+ assert(command == DECODE_COMMAND_NONE);
+ assert(state != DECODE_STATE_ERROR || error != nullptr);
+
+ return state == DECODE_STATE_ERROR
+ ? g_error_copy(error)
+ : nullptr;
+ }
+
+ /**
+ * Like dc_get_error(), but locks and unlocks the object.
+ */
+ GError *LockGetError() const {
+ Lock();
+ GError *result = GetError();
+ Unlock();
+ return result;
+ }
+
+ /**
+ * Clear the error condition and free the #GError object (if any).
+ *
+ * Caller must lock the object.
+ */
+ void ClearError() {
+ if (state == DECODE_STATE_ERROR) {
+ g_error_free(error);
+ state = DECODE_STATE_STOP;
+ }
+ }
+
+ /**
+ * Check if the specified song is currently being decoded. If the
+ * decoder is not running currently (or being started), then this
+ * function returns false in any case.
+ *
+ * Caller must lock the object.
+ */
+ gcc_pure
+ bool IsCurrentSong(const Song *_song) const;
+
+ gcc_pure
+ bool LockIsCurrentSong(const Song *_song) const {
+ Lock();
+ const bool result = IsCurrentSong(_song);
+ Unlock();
+ return result;
+ }
+
+ /**
+ * Start the decoder.
+ *
+ * @param song the song to be decoded; the given instance will be
+ * owned and freed by the decoder
+ * @param start_ms see #decoder_control
+ * @param end_ms see #decoder_control
+ * @param pipe the pipe which receives the decoded chunks (owned by
+ * the caller)
+ */
+ void Start(Song *song, unsigned start_ms, unsigned end_ms,
+ music_buffer *buffer, music_pipe *pipe);
+
+ void Stop();
+
+ bool Seek(double where);
+
+ void Quit();
+
+ void MixRampStart(char *_mixramp_start);
+ void MixRampEnd(char *_mixramp_end);
+ void MixRampPrevEnd(char *_mixramp_prev_end);
+};
+
+#endif
diff --git a/src/DecoderError.hxx b/src/DecoderError.hxx
new file mode 100644
index 000000000..14810dec7
--- /dev/null
+++ b/src/DecoderError.hxx
@@ -0,0 +1,35 @@
+/*
+ * 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_DECODER_ERROR_HXX
+#define MPD_DECODER_ERROR_HXX
+
+#include <glib.h>
+
+/**
+ * Quark for GError.domain.
+ */
+G_GNUC_CONST
+static inline GQuark
+decoder_quark(void)
+{
+ return g_quark_from_static_string("decoder");
+}
+
+#endif
diff --git a/src/DecoderInternal.cxx b/src/DecoderInternal.cxx
new file mode 100644
index 000000000..6c703e227
--- /dev/null
+++ b/src/DecoderInternal.cxx
@@ -0,0 +1,106 @@
+/*
+ * 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 "DecoderInternal.hxx"
+#include "DecoderControl.hxx"
+#include "MusicPipe.hxx"
+#include "MusicBuffer.hxx"
+#include "MusicChunk.hxx"
+#include "Tag.hxx"
+
+#include <assert.h>
+
+decoder::~decoder()
+{
+ /* caller must flush the chunk */
+ assert(chunk == nullptr);
+
+ delete song_tag;
+ delete stream_tag;
+ delete decoder_tag;
+}
+
+/**
+ * All chunks are full of decoded data; wait for the player to free
+ * one.
+ */
+static enum decoder_command
+need_chunks(struct decoder_control *dc, bool do_wait)
+{
+ if (dc->command == DECODE_COMMAND_STOP ||
+ dc->command == DECODE_COMMAND_SEEK)
+ return dc->command;
+
+ if (do_wait) {
+ dc->Wait();
+ dc->client_cond.signal();
+
+ return dc->command;
+ }
+
+ return DECODE_COMMAND_NONE;
+}
+
+struct music_chunk *
+decoder_get_chunk(struct decoder *decoder)
+{
+ struct decoder_control *dc = decoder->dc;
+ enum decoder_command cmd;
+
+ assert(decoder != NULL);
+
+ if (decoder->chunk != NULL)
+ return decoder->chunk;
+
+ do {
+ decoder->chunk = music_buffer_allocate(dc->buffer);
+ if (decoder->chunk != NULL) {
+ 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;
+ }
+
+ dc->Lock();
+ cmd = need_chunks(dc, true);
+ dc->Unlock();
+ } while (cmd == DECODE_COMMAND_NONE);
+
+ return NULL;
+}
+
+void
+decoder_flush_chunk(struct decoder *decoder)
+{
+ struct decoder_control *dc = decoder->dc;
+
+ assert(decoder != NULL);
+ assert(decoder->chunk != NULL);
+
+ if (decoder->chunk->IsEmpty())
+ music_buffer_return(dc->buffer, decoder->chunk);
+ else
+ music_pipe_push(dc->pipe, decoder->chunk);
+
+ decoder->chunk = NULL;
+}
diff --git a/src/DecoderInternal.hxx b/src/DecoderInternal.hxx
new file mode 100644
index 000000000..3715ef427
--- /dev/null
+++ b/src/DecoderInternal.hxx
@@ -0,0 +1,114 @@
+/*
+ * 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_DECODER_INTERNAL_HXX
+#define MPD_DECODER_INTERNAL_HXX
+
+#include "DecoderCommand.hxx"
+#include "pcm/PcmConvert.hxx"
+#include "replay_gain_info.h"
+
+struct input_stream;
+struct Tag;
+
+struct decoder {
+ struct decoder_control *dc;
+
+ PcmConvert conv_state;
+
+ /**
+ * The time stamp of the next data chunk, in seconds.
+ */
+ double timestamp;
+
+ /**
+ * Is the initial seek (to the start position of the sub-song)
+ * pending, or has it been performed already?
+ */
+ bool initial_seek_pending;
+
+ /**
+ * Is the initial seek currently running? During this time,
+ * the decoder command is SEEK. This flag is set by
+ * decoder_get_virtual_command(), when the virtual SEEK
+ * command is generated for the first time.
+ */
+ bool initial_seek_running;
+
+ /**
+ * This flag is set by decoder_seek_where(), and checked by
+ * decoder_command_finished(). It is used to clean up after
+ * seeking.
+ */
+ bool seeking;
+
+ /**
+ * The tag from the song object. This is only used for local
+ * files, because we expect the stream server to send us a new
+ * tag each time we play it.
+ */
+ Tag *song_tag;
+
+ /** the last tag received from the stream */
+ Tag *stream_tag;
+
+ /** the last tag received from the decoder plugin */
+ Tag *decoder_tag;
+
+ /** the chunk currently being written to */
+ struct music_chunk *chunk;
+
+ struct replay_gain_info replay_gain_info;
+
+ /**
+ * A positive serial number for checking if replay gain info
+ * has changed since the last check.
+ */
+ unsigned replay_gain_serial;
+
+ decoder(decoder_control *_dc, bool _initial_seek_pending, Tag *_tag)
+ :dc(_dc),
+ timestamp(0),
+ initial_seek_pending(_initial_seek_pending),
+ initial_seek_running(false),
+ seeking(false),
+ song_tag(_tag), stream_tag(nullptr), decoder_tag(nullptr),
+ chunk(nullptr),
+ replay_gain_serial(0) {
+ }
+
+ ~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(struct decoder *decoder);
+
+/**
+ * Flushes the current chunk.
+ */
+void
+decoder_flush_chunk(struct decoder *decoder);
+
+#endif
diff --git a/src/DecoderList.cxx b/src/DecoderList.cxx
new file mode 100644
index 000000000..5c1a486df
--- /dev/null
+++ b/src/DecoderList.cxx
@@ -0,0 +1,240 @@
+/*
+ * 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 "DecoderList.hxx"
+#include "DecoderPlugin.hxx"
+#include "conf.h"
+#include "mpd_error.h"
+#include "decoder/AudiofileDecoderPlugin.hxx"
+#include "decoder/PcmDecoderPlugin.hxx"
+#include "decoder/DsdiffDecoderPlugin.hxx"
+#include "decoder/DsfDecoderPlugin.hxx"
+#include "decoder/FlacDecoderPlugin.h"
+#include "decoder/OpusDecoderPlugin.h"
+#include "decoder/VorbisDecoderPlugin.h"
+#include "decoder/AdPlugDecoderPlugin.h"
+#include "decoder/WavpackDecoderPlugin.hxx"
+#include "decoder/FfmpegDecoderPlugin.hxx"
+#include "decoder/GmeDecoderPlugin.hxx"
+#include "decoder/FaadDecoderPlugin.hxx"
+#include "decoder/MadDecoderPlugin.hxx"
+#include "decoder/SndfileDecoderPlugin.hxx"
+#include "decoder/Mpg123DecoderPlugin.hxx"
+#include "decoder/WildmidiDecoderPlugin.hxx"
+#include "decoder/MikmodDecoderPlugin.hxx"
+#include "decoder/ModplugDecoderPlugin.hxx"
+#include "decoder/MpcdecDecoderPlugin.hxx"
+#include "decoder/FluidsynthDecoderPlugin.hxx"
+
+#include <glib.h>
+
+#include <string.h>
+
+extern const struct decoder_plugin sidplay_decoder_plugin;
+
+const struct decoder_plugin *const decoder_plugins[] = {
+#ifdef HAVE_MAD
+ &mad_decoder_plugin,
+#endif
+#ifdef HAVE_MPG123
+ &mpg123_decoder_plugin,
+#endif
+#ifdef ENABLE_VORBIS_DECODER
+ &vorbis_decoder_plugin,
+#endif
+#if defined(HAVE_FLAC)
+ &oggflac_decoder_plugin,
+#endif
+#ifdef HAVE_FLAC
+ &flac_decoder_plugin,
+#endif
+#ifdef HAVE_OPUS
+ &opus_decoder_plugin,
+#endif
+#ifdef ENABLE_SNDFILE
+ &sndfile_decoder_plugin,
+#endif
+#ifdef HAVE_AUDIOFILE
+ &audiofile_decoder_plugin,
+#endif
+ &dsdiff_decoder_plugin,
+ &dsf_decoder_plugin,
+#ifdef HAVE_FAAD
+ &faad_decoder_plugin,
+#endif
+#ifdef HAVE_MPCDEC
+ &mpcdec_decoder_plugin,
+#endif
+#ifdef HAVE_WAVPACK
+ &wavpack_decoder_plugin,
+#endif
+#ifdef HAVE_MODPLUG
+ &modplug_decoder_plugin,
+#endif
+#ifdef ENABLE_MIKMOD_DECODER
+ &mikmod_decoder_plugin,
+#endif
+#ifdef ENABLE_SIDPLAY
+ &sidplay_decoder_plugin,
+#endif
+#ifdef ENABLE_WILDMIDI
+ &wildmidi_decoder_plugin,
+#endif
+#ifdef ENABLE_FLUIDSYNTH
+ &fluidsynth_decoder_plugin,
+#endif
+#ifdef HAVE_ADPLUG
+ &adplug_decoder_plugin,
+#endif
+#ifdef HAVE_FFMPEG
+ &ffmpeg_decoder_plugin,
+#endif
+#ifdef HAVE_GME
+ &gme_decoder_plugin,
+#endif
+ &pcm_decoder_plugin,
+ NULL
+};
+
+enum {
+ num_decoder_plugins = G_N_ELEMENTS(decoder_plugins) - 1,
+};
+
+/** which plugins have been initialized successfully? */
+bool decoder_plugins_enabled[num_decoder_plugins];
+
+static unsigned
+decoder_plugin_index(const struct decoder_plugin *plugin)
+{
+ unsigned i = 0;
+
+ while (decoder_plugins[i] != plugin)
+ ++i;
+
+ return i;
+}
+
+static unsigned
+decoder_plugin_next_index(const struct decoder_plugin *plugin)
+{
+ return plugin == 0
+ ? 0 /* start with first plugin */
+ : decoder_plugin_index(plugin) + 1;
+}
+
+const struct decoder_plugin *
+decoder_plugin_from_suffix(const char *suffix,
+ const struct decoder_plugin *plugin)
+{
+ if (suffix == NULL)
+ return NULL;
+
+ for (unsigned i = decoder_plugin_next_index(plugin);
+ decoder_plugins[i] != NULL; ++i) {
+ plugin = decoder_plugins[i];
+ if (decoder_plugins_enabled[i] &&
+ decoder_plugin_supports_suffix(plugin, suffix))
+ return plugin;
+ }
+
+ return NULL;
+}
+
+const struct decoder_plugin *
+decoder_plugin_from_mime_type(const char *mimeType, unsigned int next)
+{
+ static unsigned i = num_decoder_plugins;
+
+ if (mimeType == NULL)
+ return NULL;
+
+ if (!next)
+ i = 0;
+ for (; decoder_plugins[i] != NULL; ++i) {
+ const struct decoder_plugin *plugin = decoder_plugins[i];
+ if (decoder_plugins_enabled[i] &&
+ decoder_plugin_supports_mime_type(plugin, mimeType)) {
+ ++i;
+ return plugin;
+ }
+ }
+
+ return NULL;
+}
+
+const struct decoder_plugin *
+decoder_plugin_from_name(const char *name)
+{
+ decoder_plugins_for_each_enabled(plugin)
+ if (strcmp(plugin->name, name) == 0)
+ return plugin;
+
+ return NULL;
+}
+
+/**
+ * Find the "decoder" configuration block for the specified plugin.
+ *
+ * @param plugin_name the name of the decoder plugin
+ * @return the configuration block, or NULL if none was configured
+ */
+static const struct config_param *
+decoder_plugin_config(const char *plugin_name)
+{
+ const struct config_param *param = NULL;
+
+ while ((param = config_get_next_param(CONF_DECODER, param)) != NULL) {
+ const char *name = param->GetBlockValue("plugin");
+ if (name == NULL)
+ MPD_ERROR("decoder configuration without 'plugin' name in line %d",
+ param->line);
+
+ if (strcmp(name, plugin_name) == 0)
+ return param;
+ }
+
+ return NULL;
+}
+
+void decoder_plugin_init_all(void)
+{
+ struct config_param empty;
+
+ for (unsigned i = 0; decoder_plugins[i] != NULL; ++i) {
+ const struct decoder_plugin *plugin = decoder_plugins[i];
+ const struct config_param *param =
+ decoder_plugin_config(plugin->name);
+
+ if (param == nullptr)
+ param = &empty;
+ else if (!param->GetBlockValue("enabled", true))
+ /* the plugin is disabled in mpd.conf */
+ continue;
+
+ if (decoder_plugin_init(plugin, *param))
+ decoder_plugins_enabled[i] = true;
+ }
+}
+
+void decoder_plugin_deinit_all(void)
+{
+ decoder_plugins_for_each_enabled(plugin)
+ decoder_plugin_finish(plugin);
+}
diff --git a/src/DecoderList.hxx b/src/DecoderList.hxx
new file mode 100644
index 000000000..8dab8724e
--- /dev/null
+++ b/src/DecoderList.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_DECODER_LIST_HXX
+#define MPD_DECODER_LIST_HXX
+
+struct decoder_plugin;
+
+extern const struct decoder_plugin *const decoder_plugins[];
+extern bool decoder_plugins_enabled[];
+
+#define decoder_plugins_for_each(plugin) \
+ for (const struct decoder_plugin *plugin, \
+ *const*decoder_plugin_iterator = &decoder_plugins[0]; \
+ (plugin = *decoder_plugin_iterator) != NULL; \
+ ++decoder_plugin_iterator)
+
+#define decoder_plugins_for_each_enabled(plugin) \
+ decoder_plugins_for_each(plugin) \
+ if (decoder_plugins_enabled[decoder_plugin_iterator - decoder_plugins])
+
+/* 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 NULL to find the first plugin
+ * @return a plugin, or NULL if none matches
+ */
+const struct decoder_plugin *
+decoder_plugin_from_suffix(const char *suffix,
+ const struct decoder_plugin *plugin);
+
+const struct decoder_plugin *
+decoder_plugin_from_mime_type(const char *mimeType, unsigned int next);
+
+const struct decoder_plugin *
+decoder_plugin_from_name(const char *name);
+
+/* this is where we "load" all the "plugins" ;-) */
+void decoder_plugin_init_all(void);
+
+/* this is where we "unload" all the "plugins" */
+void decoder_plugin_deinit_all(void);
+
+#endif
diff --git a/src/DecoderPlugin.cxx b/src/DecoderPlugin.cxx
new file mode 100644
index 000000000..9dce4b21f
--- /dev/null
+++ b/src/DecoderPlugin.cxx
@@ -0,0 +1,47 @@
+/*
+ * 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 "DecoderPlugin.hxx"
+#include "util/StringUtil.hxx"
+
+#include <assert.h>
+
+bool
+decoder_plugin_supports_suffix(const struct decoder_plugin *plugin,
+ const char *suffix)
+{
+ assert(plugin != nullptr);
+ assert(suffix != nullptr);
+
+ return plugin->suffixes != nullptr &&
+ string_array_contains(plugin->suffixes, suffix);
+
+}
+
+bool
+decoder_plugin_supports_mime_type(const struct decoder_plugin *plugin,
+ const char *mime_type)
+{
+ assert(plugin != nullptr);
+ assert(mime_type != nullptr);
+
+ return plugin->mime_types != nullptr &&
+ string_array_contains(plugin->mime_types, mime_type);
+}
diff --git a/src/DecoderPlugin.hxx b/src/DecoderPlugin.hxx
new file mode 100644
index 000000000..687447dee
--- /dev/null
+++ b/src/DecoderPlugin.hxx
@@ -0,0 +1,212 @@
+/*
+ * 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_DECODER_PLUGIN_HXX
+#define MPD_DECODER_PLUGIN_HXX
+
+struct config_param;
+struct input_stream;
+struct Tag;
+struct tag_handler;
+
+/**
+ * Opaque handle which the decoder plugin passes to the functions in
+ * this header.
+ */
+struct decoder;
+
+struct decoder_plugin {
+ const char *name;
+
+ /**
+ * Initialize the decoder plugin. Optional method.
+ *
+ * @param param a configuration block for this plugin, or nullptr
+ * if none is configured
+ * @return true if the plugin was initialized successfully,
+ * false if the plugin is not available
+ */
+ bool (*init)(const config_param &param);
+
+ /**
+ * Deinitialize a decoder plugin which was initialized
+ * successfully. Optional method.
+ */
+ void (*finish)(void);
+
+ /**
+ * Decode a stream (data read from an #input_stream object).
+ *
+ * Either implement this method or file_decode(). If
+ * possible, it is recommended to implement this method,
+ * because it is more versatile.
+ */
+ void (*stream_decode)(struct decoder *decoder,
+ struct input_stream *is);
+
+ /**
+ * Decode a local file.
+ *
+ * Either implement this method or stream_decode().
+ */
+ void (*file_decode)(struct decoder *decoder, const char *path_fs);
+
+ /**
+ * Scan metadata of a file.
+ *
+ * @return false if the operation has failed
+ */
+ bool (*scan_file)(const char *path_fs,
+ const struct tag_handler *handler,
+ void *handler_ctx);
+
+ /**
+ * Scan metadata of a file.
+ *
+ * @return false if the operation has failed
+ */
+ bool (*scan_stream)(struct input_stream *is,
+ const struct tag_handler *handler,
+ void *handler_ctx);
+
+ /**
+ * @brief Return a "virtual" filename for subtracks in
+ * container formats like flac
+ * @param const char* pathname full pathname for the file on fs
+ * @param const unsigned int tnum track number
+ *
+ * @return nullptr if there are no multiple files
+ * a filename for every single track according to tnum (param 2)
+ * do not include full pathname here, just the "virtual" file
+ */
+ char* (*container_scan)(const char *path_fs, const unsigned int tnum);
+
+ /* last element in these arrays must always be a nullptr: */
+ const char *const*suffixes;
+ const char *const*mime_types;
+};
+
+/**
+ * Initialize a decoder plugin.
+ *
+ * @param param a configuration block for this plugin, or nullptr if none
+ * is configured
+ * @return true if the plugin was initialized successfully, false if
+ * the plugin is not available
+ */
+static inline bool
+decoder_plugin_init(const struct decoder_plugin *plugin,
+ const config_param &param)
+{
+ return plugin->init != nullptr
+ ? plugin->init(param)
+ : true;
+}
+
+/**
+ * Deinitialize a decoder plugin which was initialized successfully.
+ */
+static inline void
+decoder_plugin_finish(const struct decoder_plugin *plugin)
+{
+ if (plugin->finish != nullptr)
+ plugin->finish();
+}
+
+/**
+ * Decode a stream.
+ */
+static inline void
+decoder_plugin_stream_decode(const struct decoder_plugin *plugin,
+ struct decoder *decoder, struct input_stream *is)
+{
+ plugin->stream_decode(decoder, is);
+}
+
+/**
+ * Decode a file.
+ */
+static inline void
+decoder_plugin_file_decode(const struct decoder_plugin *plugin,
+ struct decoder *decoder, const char *path_fs)
+{
+ plugin->file_decode(decoder, path_fs);
+}
+
+/**
+ * Read the tag of a file.
+ */
+static inline bool
+decoder_plugin_scan_file(const struct decoder_plugin *plugin,
+ const char *path_fs,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ return plugin->scan_file != nullptr
+ ? plugin->scan_file(path_fs, handler, handler_ctx)
+ : false;
+}
+
+/**
+ * Read the tag of a stream.
+ */
+static inline bool
+decoder_plugin_scan_stream(const struct decoder_plugin *plugin,
+ struct input_stream *is,
+ const struct tag_handler *handler,
+ void *handler_ctx)
+{
+ return plugin->scan_stream != nullptr
+ ? plugin->scan_stream(is, handler, handler_ctx)
+ : false;
+}
+
+/**
+ * return "virtual" tracks in a container
+ */
+static inline char *
+decoder_plugin_container_scan( const struct decoder_plugin *plugin,
+ const char* pathname,
+ const unsigned int tnum)
+{
+ return plugin->container_scan(pathname, tnum);
+}
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Does the plugin announce the specified file name suffix?
+ */
+bool
+decoder_plugin_supports_suffix(const struct decoder_plugin *plugin,
+ const char *suffix);
+
+/**
+ * Does the plugin announce the specified MIME type?
+ */
+bool
+decoder_plugin_supports_mime_type(const struct decoder_plugin *plugin,
+ const char *mime_type);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/DecoderPrint.cxx b/src/DecoderPrint.cxx
new file mode 100644
index 000000000..3f7f94937
--- /dev/null
+++ b/src/DecoderPrint.cxx
@@ -0,0 +1,53 @@
+/*
+ * 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 "DecoderPrint.hxx"
+#include "DecoderList.hxx"
+#include "DecoderPlugin.hxx"
+#include "Client.hxx"
+
+#include <assert.h>
+
+static void
+decoder_plugin_print(Client *client,
+ const struct decoder_plugin *plugin)
+{
+ const char *const*p;
+
+ assert(plugin != NULL);
+ assert(plugin->name != NULL);
+
+ client_printf(client, "plugin: %s\n", plugin->name);
+
+ if (plugin->suffixes != NULL)
+ for (p = plugin->suffixes; *p != NULL; ++p)
+ client_printf(client, "suffix: %s\n", *p);
+
+ if (plugin->mime_types != NULL)
+ for (p = plugin->mime_types; *p != NULL; ++p)
+ client_printf(client, "mime_type: %s\n", *p);
+}
+
+void
+decoder_list_print(Client *client)
+{
+ decoder_plugins_for_each_enabled(plugin)
+ decoder_plugin_print(client, plugin);
+}
diff --git a/src/DecoderPrint.hxx b/src/DecoderPrint.hxx
new file mode 100644
index 000000000..d94ba2cef
--- /dev/null
+++ b/src/DecoderPrint.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_DECODER_PRINT_HXX
+#define MPD_DECODER_PRINT_HXX
+
+class Client;
+
+void
+decoder_list_print(Client *client);
+
+#endif
diff --git a/src/DecoderThread.cxx b/src/DecoderThread.cxx
new file mode 100644
index 000000000..3ebd5653e
--- /dev/null
+++ b/src/DecoderThread.cxx
@@ -0,0 +1,506 @@
+/*
+ * 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 "DecoderThread.hxx"
+#include "DecoderControl.hxx"
+#include "DecoderInternal.hxx"
+#include "DecoderError.hxx"
+#include "DecoderPlugin.hxx"
+#include "Song.hxx"
+#include "mpd_error.h"
+#include "Mapper.hxx"
+#include "fs/Path.hxx"
+#include "DecoderAPI.hxx"
+#include "Tag.hxx"
+#include "InputStream.hxx"
+#include "DecoderList.hxx"
+#include "util/UriUtil.hxx"
+#include "ApeReplayGain.hxx"
+
+#include <glib.h>
+
+#include <unistd.h>
+#include <stdio.h> /* for SEEK_SET */
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "decoder_thread"
+
+/**
+ * Marks the current decoder command as "finished" and notifies the
+ * player thread.
+ *
+ * @param dc the #decoder_control object; must be locked
+ */
+static void
+decoder_command_finished_locked(struct decoder_control *dc)
+{
+ assert(dc->command != DECODE_COMMAND_NONE);
+
+ dc->command = DECODE_COMMAND_NONE;
+
+ dc->client_cond.signal();
+}
+
+/**
+ * Opens the input stream with input_stream_open(), and waits until
+ * the stream gets ready. If a decoder STOP command is received
+ * during that, it cancels the operation (but does not close the
+ * stream).
+ *
+ * Unlock the decoder before calling this function.
+ *
+ * @return an input_stream on success or if #DECODE_COMMAND_STOP is
+ * received, NULL on error
+ */
+static struct input_stream *
+decoder_input_stream_open(struct decoder_control *dc, const char *uri)
+{
+ GError *error = NULL;
+ struct input_stream *is;
+
+ is = input_stream_open(uri, dc->mutex, dc->cond, &error);
+ if (is == NULL) {
+ if (error != NULL) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ }
+
+ return NULL;
+ }
+
+ /* wait for the input stream to become ready; its metadata
+ will be available then */
+
+ dc->Lock();
+
+ input_stream_update(is);
+ while (!is->ready &&
+ dc->command != DECODE_COMMAND_STOP) {
+ dc->Wait();
+
+ input_stream_update(is);
+ }
+
+ if (!input_stream_check(is, &error)) {
+ dc->Unlock();
+
+ g_warning("%s", error->message);
+ g_error_free(error);
+
+ return NULL;
+ }
+
+ dc->Unlock();
+
+ return is;
+}
+
+static bool
+decoder_stream_decode(const struct decoder_plugin *plugin,
+ struct decoder *decoder,
+ struct input_stream *input_stream)
+{
+ assert(plugin != NULL);
+ assert(plugin->stream_decode != NULL);
+ assert(decoder != NULL);
+ assert(decoder->stream_tag == NULL);
+ assert(decoder->decoder_tag == NULL);
+ assert(input_stream != NULL);
+ assert(input_stream->ready);
+ assert(decoder->dc->state == DECODE_STATE_START);
+
+ g_debug("probing plugin %s", plugin->name);
+
+ if (decoder->dc->command == DECODE_COMMAND_STOP)
+ return true;
+
+ /* rewind the stream, so each plugin gets a fresh start */
+ input_stream_seek(input_stream, 0, SEEK_SET, NULL);
+
+ decoder->dc->Unlock();
+
+ decoder_plugin_stream_decode(plugin, decoder, input_stream);
+
+ decoder->dc->Lock();
+
+ assert(decoder->dc->state == DECODE_STATE_START ||
+ decoder->dc->state == DECODE_STATE_DECODE);
+
+ return decoder->dc->state != DECODE_STATE_START;
+}
+
+static bool
+decoder_file_decode(const struct decoder_plugin *plugin,
+ struct decoder *decoder, const char *path)
+{
+ assert(plugin != NULL);
+ assert(plugin->file_decode != NULL);
+ assert(decoder != NULL);
+ assert(decoder->stream_tag == NULL);
+ assert(decoder->decoder_tag == NULL);
+ assert(path != NULL);
+ assert(g_path_is_absolute(path));
+ assert(decoder->dc->state == DECODE_STATE_START);
+
+ g_debug("probing plugin %s", plugin->name);
+
+ if (decoder->dc->command == DECODE_COMMAND_STOP)
+ return true;
+
+ decoder->dc->Unlock();
+
+ decoder_plugin_file_decode(plugin, decoder, path);
+
+ decoder->dc->Lock();
+
+ assert(decoder->dc->state == DECODE_STATE_START ||
+ decoder->dc->state == DECODE_STATE_DECODE);
+
+ return decoder->dc->state != DECODE_STATE_START;
+}
+
+/**
+ * Hack to allow tracking const decoder plugins in a GSList.
+ */
+static inline gpointer
+deconst_plugin(const struct decoder_plugin *plugin)
+{
+ return const_cast<struct decoder_plugin *>(plugin);
+}
+
+/**
+ * Try decoding a stream, using plugins matching the stream's MIME type.
+ *
+ * @param tried_r a list of plugins which were tried
+ */
+static bool
+decoder_run_stream_mime_type(struct decoder *decoder, struct input_stream *is,
+ GSList **tried_r)
+{
+ assert(tried_r != NULL);
+
+ const struct decoder_plugin *plugin;
+ unsigned int next = 0;
+
+ if (is->mime.empty())
+ return false;
+
+ while ((plugin = decoder_plugin_from_mime_type(is->mime.c_str(),
+ next++))) {
+ if (plugin->stream_decode == NULL)
+ continue;
+
+ if (g_slist_find(*tried_r, plugin) != NULL)
+ /* don't try a plugin twice */
+ continue;
+
+ if (decoder_stream_decode(plugin, decoder, is))
+ return true;
+
+ *tried_r = g_slist_prepend(*tried_r, deconst_plugin(plugin));
+ }
+
+ return false;
+}
+
+/**
+ * Try decoding a stream, using plugins matching the stream's URI
+ * suffix.
+ *
+ * @param tried_r a list of plugins which were tried
+ */
+static bool
+decoder_run_stream_suffix(struct decoder *decoder, struct input_stream *is,
+ const char *uri, GSList **tried_r)
+{
+ assert(tried_r != NULL);
+
+ const char *suffix = uri_get_suffix(uri);
+ const struct decoder_plugin *plugin = NULL;
+
+ if (suffix == NULL)
+ return false;
+
+ while ((plugin = decoder_plugin_from_suffix(suffix, plugin)) != NULL) {
+ if (plugin->stream_decode == NULL)
+ continue;
+
+ if (g_slist_find(*tried_r, plugin) != NULL)
+ /* don't try a plugin twice */
+ continue;
+
+ if (decoder_stream_decode(plugin, decoder, is))
+ return true;
+
+ *tried_r = g_slist_prepend(*tried_r, deconst_plugin(plugin));
+ }
+
+ return false;
+}
+
+/**
+ * Try decoding a stream, using the fallback plugin.
+ */
+static bool
+decoder_run_stream_fallback(struct decoder *decoder, struct input_stream *is)
+{
+ const struct decoder_plugin *plugin;
+
+ plugin = decoder_plugin_from_name("mad");
+ return plugin != NULL && plugin->stream_decode != NULL &&
+ decoder_stream_decode(plugin, decoder, is);
+}
+
+/**
+ * Try decoding a stream.
+ */
+static bool
+decoder_run_stream(struct decoder *decoder, const char *uri)
+{
+ struct decoder_control *dc = decoder->dc;
+ struct input_stream *input_stream;
+ bool success;
+
+ dc->Unlock();
+
+ input_stream = decoder_input_stream_open(dc, uri);
+ if (input_stream == NULL) {
+ dc->Lock();
+ return false;
+ }
+
+ dc->Lock();
+
+ GSList *tried = NULL;
+
+ success = dc->command == DECODE_COMMAND_STOP ||
+ /* first we try mime types: */
+ decoder_run_stream_mime_type(decoder, input_stream, &tried) ||
+ /* if that fails, try suffix matching the URL: */
+ decoder_run_stream_suffix(decoder, input_stream, uri,
+ &tried) ||
+ /* fallback to mp3: this is needed for bastard streams
+ that don't have a suffix or set the mimeType */
+ (tried == NULL &&
+ decoder_run_stream_fallback(decoder, input_stream));
+
+ g_slist_free(tried);
+
+ dc->Unlock();
+ input_stream_close(input_stream);
+ dc->Lock();
+
+ return success;
+}
+
+/**
+ * Attempt to load replay gain data, and pass it to
+ * decoder_replay_gain().
+ */
+static void
+decoder_load_replay_gain(struct decoder *decoder, const char *path_fs)
+{
+ struct replay_gain_info info;
+ if (replay_gain_ape_read(path_fs, &info))
+ decoder_replay_gain(decoder, &info);
+}
+
+/**
+ * Try decoding a file.
+ */
+static bool
+decoder_run_file(struct decoder *decoder, const char *path_fs)
+{
+ struct decoder_control *dc = decoder->dc;
+ const char *suffix = uri_get_suffix(path_fs);
+ const struct decoder_plugin *plugin = NULL;
+
+ if (suffix == NULL)
+ return false;
+
+ dc->Unlock();
+
+ decoder_load_replay_gain(decoder, path_fs);
+
+ while ((plugin = decoder_plugin_from_suffix(suffix, plugin)) != NULL) {
+ if (plugin->file_decode != NULL) {
+ dc->Lock();
+
+ if (decoder_file_decode(plugin, decoder, path_fs))
+ return true;
+
+ dc->Unlock();
+ } else if (plugin->stream_decode != NULL) {
+ struct input_stream *input_stream;
+ bool success;
+
+ input_stream = decoder_input_stream_open(dc, path_fs);
+ if (input_stream == NULL)
+ continue;
+
+ dc->Lock();
+
+ success = decoder_stream_decode(plugin, decoder,
+ input_stream);
+
+ dc->Unlock();
+
+ input_stream_close(input_stream);
+
+ if (success) {
+ dc->Lock();
+ return true;
+ }
+ }
+ }
+
+ dc->Lock();
+ return false;
+}
+
+static void
+decoder_run_song(struct decoder_control *dc,
+ const Song *song, const char *uri)
+{
+ decoder decoder(dc, dc->start_ms > 0,
+ song->tag != NULL && song->IsFile()
+ ? new Tag(*song->tag) : nullptr);
+ int ret;
+
+ dc->state = DECODE_STATE_START;
+
+ decoder_command_finished_locked(dc);
+
+ ret = song->IsFile()
+ ? decoder_run_file(&decoder, uri)
+ : decoder_run_stream(&decoder, uri);
+
+ dc->Unlock();
+
+ /* flush the last chunk */
+
+ if (decoder.chunk != NULL)
+ decoder_flush_chunk(&decoder);
+
+ dc->Lock();
+
+ if (ret)
+ dc->state = DECODE_STATE_STOP;
+ else {
+ dc->state = DECODE_STATE_ERROR;
+
+ const char *error_uri = song->uri;
+ char *allocated = uri_remove_auth(error_uri);
+ if (allocated != NULL)
+ error_uri = allocated;
+
+ dc->error = g_error_new(decoder_quark(), 0,
+ "Failed to decode %s", error_uri);
+ g_free(allocated);
+ }
+
+ dc->client_cond.signal();
+}
+
+static void
+decoder_run(struct decoder_control *dc)
+{
+ dc->ClearError();
+
+ const Song *song = dc->song;
+ char *uri;
+
+ assert(song != NULL);
+
+ if (song->IsFile())
+ uri = map_song_fs(song).Steal();
+ else
+ uri = song->GetURI();
+
+ if (uri == NULL) {
+ dc->state = DECODE_STATE_ERROR;
+ dc->error = g_error_new(decoder_quark(), 0,
+ "Failed to map song");
+
+ decoder_command_finished_locked(dc);
+ return;
+ }
+
+ decoder_run_song(dc, song, uri);
+ g_free(uri);
+
+}
+
+static gpointer
+decoder_task(gpointer arg)
+{
+ struct decoder_control *dc = (struct decoder_control *)arg;
+
+ dc->Lock();
+
+ do {
+ assert(dc->state == DECODE_STATE_STOP ||
+ dc->state == DECODE_STATE_ERROR);
+
+ switch (dc->command) {
+ case DECODE_COMMAND_START:
+ dc->MixRampStart(nullptr);
+ dc->MixRampPrevEnd(dc->mixramp_end);
+ dc->mixramp_end = NULL; /* Don't free, it's copied above. */
+ dc->replay_gain_prev_db = dc->replay_gain_db;
+ dc->replay_gain_db = 0;
+
+ /* fall through */
+
+ case DECODE_COMMAND_SEEK:
+ decoder_run(dc);
+ break;
+
+ case DECODE_COMMAND_STOP:
+ decoder_command_finished_locked(dc);
+ break;
+
+ case DECODE_COMMAND_NONE:
+ dc->Wait();
+ break;
+ }
+ } while (dc->command != DECODE_COMMAND_NONE || !dc->quit);
+
+ dc->Unlock();
+
+ return NULL;
+}
+
+void
+decoder_thread_start(struct decoder_control *dc)
+{
+ assert(dc->thread == NULL);
+
+ dc->quit = false;
+
+#if GLIB_CHECK_VERSION(2,32,0)
+ dc->thread = g_thread_new("thread", decoder_task, dc);
+#else
+ GError *e = NULL;
+ dc->thread = g_thread_create(decoder_task, dc, true, &e);
+ if (dc->thread == NULL)
+ MPD_ERROR("Failed to spawn decoder task: %s", e->message);
+#endif
+}
diff --git a/src/DecoderThread.hxx b/src/DecoderThread.hxx
new file mode 100644
index 000000000..8efaa2fca
--- /dev/null
+++ b/src/DecoderThread.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_DECODER_THREAD_HXX
+#define MPD_DECODER_THREAD_HXX
+
+struct decoder_control;
+
+void
+decoder_thread_start(struct decoder_control *dc);
+
+#endif
diff --git a/src/DespotifyUtils.cxx b/src/DespotifyUtils.cxx
new file mode 100644
index 000000000..c45722379
--- /dev/null
+++ b/src/DespotifyUtils.cxx
@@ -0,0 +1,144 @@
+/*
+ * 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 "DespotifyUtils.hxx"
+#include "Tag.hxx"
+#include "conf.h"
+
+#include <glib.h>
+
+extern "C" {
+#include <despotify.h>
+}
+
+static struct despotify_session *g_session;
+static void (*registered_callbacks[8])(struct despotify_session *,
+ int, void *, void *);
+static void *registered_callback_data[8];
+
+static void callback(struct despotify_session* ds, int sig,
+ void* data, G_GNUC_UNUSED void* callback_data)
+{
+ size_t i;
+
+ for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) {
+ void (*cb)(struct despotify_session *, int, void *, void *) = registered_callbacks[i];
+ void *cb_data = registered_callback_data[i];
+
+ if (cb)
+ cb(ds, sig, data, cb_data);
+ }
+}
+
+bool mpd_despotify_register_callback(void (*cb)(struct despotify_session *, int, void *, void *),
+ void *cb_data)
+{
+ size_t i;
+
+ for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) {
+
+ if (!registered_callbacks[i]) {
+ registered_callbacks[i] = cb;
+ registered_callback_data[i] = cb_data;
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void mpd_despotify_unregister_callback(void (*cb)(struct despotify_session *, int, void *, void *))
+{
+ size_t i;
+
+ for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) {
+
+ if (registered_callbacks[i] == cb) {
+ registered_callbacks[i] = NULL;
+ }
+ }
+}
+
+
+Tag *
+mpd_despotify_tag_from_track(struct ds_track *track)
+{
+ char tracknum[20];
+ char comment[80];
+ char date[20];
+
+ Tag *tag = new Tag();
+
+ if (!track->has_meta_data)
+ return tag;
+
+ g_snprintf(tracknum, sizeof(tracknum), "%d", track->tracknumber);
+ g_snprintf(date, sizeof(date), "%d", track->year);
+ g_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;
+}
+
+struct despotify_session *mpd_despotify_get_session(void)
+{
+ const char *user;
+ const char *passwd;
+ bool high_bitrate;
+
+ if (g_session)
+ return g_session;
+
+ user = config_get_string(CONF_DESPOTIFY_USER, NULL);
+ passwd = config_get_string(CONF_DESPOTIFY_PASSWORD, NULL);
+ high_bitrate = config_get_bool(CONF_DESPOTIFY_HIGH_BITRATE, true);
+
+ if (user == NULL || passwd == NULL) {
+ g_debug("disabling despotify because account is not configured");
+ return nullptr;
+ }
+
+ if (!despotify_init()) {
+ g_debug("Can't initialize despotify\n");
+ return nullptr;
+ }
+
+ g_session = despotify_init_client(callback, NULL,
+ high_bitrate, true);
+ if (!g_session) {
+ g_debug("Can't initialize despotify client\n");
+ return nullptr;
+ }
+
+ if (!despotify_authenticate(g_session, user, passwd)) {
+ g_debug("Can't authenticate despotify session\n");
+ despotify_exit(g_session);
+ return nullptr;
+ }
+
+ return g_session;
+}
diff --git a/src/DespotifyUtils.hxx b/src/DespotifyUtils.hxx
new file mode 100644
index 000000000..2d78844c0
--- /dev/null
+++ b/src/DespotifyUtils.hxx
@@ -0,0 +1,69 @@
+/*
+ * 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_DESPOTIFY_H
+#define MPD_DESPOTIFY_H
+
+struct Tag;
+struct despotify_session;
+struct ds_track;
+
+/**
+ * Return the current despotify session.
+ *
+ * If the session isn't initialized, this function will initialize
+ * it and connect to Spotify.
+ *
+ * @return a pointer to the despotify session, or NULL if it can't
+ * be initialized (e.g., if the configuration isn't supplied)
+ */
+struct despotify_session *mpd_despotify_get_session(void);
+
+/**
+ * Create a MPD tags structure from a spotify track
+ *
+ * @param track the track to convert
+ *
+ * @return a pointer to the filled in tags structure
+ */
+Tag *
+mpd_despotify_tag_from_track(struct ds_track *track);
+
+/**
+ * Register a despotify callback.
+ *
+ * Despotify calls this e.g., when a track ends.
+ *
+ * @param cb the callback
+ * @param cb_data the data to pass to the callback
+ *
+ * @return true if the callback could be registered
+ */
+bool mpd_despotify_register_callback(void (*cb)(struct despotify_session *, int, void *, void *),
+ void *cb_data);
+
+/**
+ * Unregister a despotify callback.
+ *
+ * @param cb the callback to unregister.
+ */
+void mpd_despotify_unregister_callback(void (*cb)(struct despotify_session *, int, void *, void *));
+
+#endif
+
diff --git a/src/Directory.cxx b/src/Directory.cxx
new file mode 100644
index 000000000..1c1db386f
--- /dev/null
+++ b/src/Directory.cxx
@@ -0,0 +1,335 @@
+/*
+ * 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.
+ */
+
+#include "config.h"
+#include "Directory.hxx"
+#include "SongFilter.hxx"
+#include "PlaylistVector.hxx"
+#include "DatabaseLock.hxx"
+#include "SongSort.hxx"
+#include "Song.hxx"
+
+extern "C" {
+#include "util/list_sort.h"
+}
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+
+inline Directory *
+Directory::Allocate(const char *path)
+{
+ assert(path != NULL);
+
+ 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)
+{
+ INIT_LIST_HEAD(&children);
+ INIT_LIST_HEAD(&songs);
+
+ strcpy(path, _path);
+}
+
+Directory::~Directory()
+{
+ Song *song, *ns;
+ directory_for_each_song_safe(song, ns, this)
+ song->Free();
+
+ Directory *child, *n;
+ directory_for_each_child_safe(child, n, this)
+ child->Free();
+}
+
+Directory *
+Directory::NewGeneric(const char *path, Directory *parent)
+{
+ assert(path != NULL);
+ assert((*path == 0) == (parent == NULL));
+
+ Directory *directory = Allocate(path);
+
+ directory->parent = parent;
+
+ return directory;
+}
+
+void
+Directory::Free()
+{
+ this->Directory::~Directory();
+ g_free(this);
+}
+
+void
+Directory::Delete()
+{
+ assert(holding_db_lock());
+ assert(parent != nullptr);
+
+ list_del(&siblings);
+ Free();
+}
+
+const char *
+Directory::GetName() const
+{
+ assert(!IsRoot());
+ assert(path != nullptr);
+
+ const char *slash = strrchr(path, '/');
+ assert((slash == nullptr) == parent->IsRoot());
+
+ return slash != NULL
+ ? slash + 1
+ : path;
+}
+
+Directory *
+Directory::CreateChild(const char *name_utf8)
+{
+ assert(holding_db_lock());
+ assert(name_utf8 != NULL);
+ assert(*name_utf8 != 0);
+
+ char *allocated;
+ const char *path_utf8;
+ if (IsRoot()) {
+ allocated = NULL;
+ path_utf8 = name_utf8;
+ } else {
+ allocated = g_strconcat(GetPath(),
+ "/", name_utf8, NULL);
+ path_utf8 = allocated;
+ }
+
+ Directory *child = NewGeneric(path_utf8, this);
+ g_free(allocated);
+
+ list_add_tail(&child->siblings, &children);
+ return child;
+}
+
+const Directory *
+Directory::FindChild(const char *name) const
+{
+ assert(holding_db_lock());
+
+ const Directory *child;
+ directory_for_each_child(child, this)
+ if (strcmp(child->GetName(), name) == 0)
+ return child;
+
+ return NULL;
+}
+
+void
+Directory::PruneEmpty()
+{
+ assert(holding_db_lock());
+
+ Directory *child, *n;
+ directory_for_each_child_safe(child, n, this) {
+ child->PruneEmpty();
+
+ if (child->IsEmpty())
+ child->Delete();
+ }
+}
+
+Directory *
+Directory::LookupDirectory(const char *uri)
+{
+ assert(holding_db_lock());
+ assert(uri != NULL);
+
+ if (isRootDirectory(uri))
+ return this;
+
+ char *duplicated = g_strdup(uri), *name = duplicated;
+
+ Directory *d = this;
+ while (1) {
+ char *slash = strchr(name, '/');
+ if (slash == name) {
+ d = NULL;
+ break;
+ }
+
+ if (slash != NULL)
+ *slash = '\0';
+
+ d = d->FindChild(name);
+ if (d == NULL || slash == NULL)
+ break;
+
+ name = slash + 1;
+ }
+
+ g_free(duplicated);
+
+ return d;
+}
+
+void
+Directory::AddSong(Song *song)
+{
+ assert(holding_db_lock());
+ assert(song != NULL);
+ assert(song->parent == this);
+
+ list_add_tail(&song->siblings, &songs);
+}
+
+void
+Directory::RemoveSong(Song *song)
+{
+ assert(holding_db_lock());
+ assert(song != NULL);
+ assert(song->parent == this);
+
+ list_del(&song->siblings);
+}
+
+const Song *
+Directory::FindSong(const char *name_utf8) const
+{
+ assert(holding_db_lock());
+ assert(name_utf8 != NULL);
+
+ Song *song;
+ directory_for_each_song(song, this) {
+ assert(song->parent == this);
+
+ if (strcmp(song->uri, name_utf8) == 0)
+ return song;
+ }
+
+ return NULL;
+}
+
+Song *
+Directory::LookupSong(const char *uri)
+{
+ char *duplicated, *base;
+
+ assert(holding_db_lock());
+ assert(uri != NULL);
+
+ duplicated = g_strdup(uri);
+ base = strrchr(duplicated, '/');
+
+ Directory *d = this;
+ if (base != NULL) {
+ *base++ = 0;
+ d = d->LookupDirectory(duplicated);
+ if (d == nullptr) {
+ g_free(duplicated);
+ return NULL;
+ }
+ } else
+ base = duplicated;
+
+ Song *song = d->FindSong(base);
+ assert(song == NULL || song->parent == d);
+
+ g_free(duplicated);
+ return song;
+
+}
+
+static int
+directory_cmp(G_GNUC_UNUSED void *priv,
+ struct list_head *_a, struct list_head *_b)
+{
+ const Directory *a = (const Directory *)_a;
+ const Directory *b = (const Directory *)_b;
+ return g_utf8_collate(a->path, b->path);
+}
+
+void
+Directory::Sort()
+{
+ assert(holding_db_lock());
+
+ list_sort(NULL, &children, directory_cmp);
+ song_list_sort(&songs);
+
+ Directory *child;
+ directory_for_each_child(child, this)
+ child->Sort();
+}
+
+bool
+Directory::Walk(bool recursive, const SongFilter *filter,
+ VisitDirectory visit_directory, VisitSong visit_song,
+ VisitPlaylist visit_playlist,
+ GError **error_r) const
+{
+ assert(error_r == NULL || *error_r == NULL);
+
+ if (visit_song) {
+ Song *song;
+ directory_for_each_song(song, this)
+ if ((filter == nullptr || filter->Match(*song)) &&
+ !visit_song(*song, error_r))
+ return false;
+ }
+
+ if (visit_playlist) {
+ for (const PlaylistInfo &p : playlists)
+ if (!visit_playlist(p, *this, error_r))
+ return false;
+ }
+
+ Directory *child;
+ directory_for_each_child(child, this) {
+ if (visit_directory &&
+ !visit_directory(*child, error_r))
+ return false;
+
+ if (recursive &&
+ !child->Walk(recursive, filter,
+ visit_directory, visit_song, visit_playlist,
+ error_r))
+ return false;
+ }
+
+ return true;
+}
diff --git a/src/Directory.hxx b/src/Directory.hxx
new file mode 100644
index 000000000..97a16f085
--- /dev/null
+++ b/src/Directory.hxx
@@ -0,0 +1,263 @@
+/*
+ * 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_DIRECTORY_HXX
+#define MPD_DIRECTORY_HXX
+
+#include "check.h"
+#include "util/list.h"
+#include "gcc.h"
+#include "DatabaseVisitor.hxx"
+#include "PlaylistVector.hxx"
+#include "gerror.h"
+
+#include <sys/types.h>
+
+#define DEVICE_INARCHIVE (dev_t)(-1)
+#define DEVICE_CONTAINER (dev_t)(-2)
+
+#define directory_for_each_child(pos, directory) \
+ list_for_each_entry(pos, &directory->children, siblings)
+
+#define directory_for_each_child_safe(pos, n, directory) \
+ list_for_each_entry_safe(pos, n, &directory->children, siblings)
+
+#define directory_for_each_song(pos, directory) \
+ list_for_each_entry(pos, &directory->songs, siblings)
+
+#define directory_for_each_song_safe(pos, n, directory) \
+ list_for_each_entry_safe(pos, n, &directory->songs, siblings)
+
+struct Song;
+struct db_visitor;
+class SongFilter;
+
+struct Directory {
+ /**
+ * Pointers to the siblings of this directory within the
+ * parent directory. It is unused (undefined) in the root
+ * directory.
+ *
+ * This attribute is protected with the global #db_mutex.
+ * Read access in the update thread does not need protection.
+ */
+ struct list_head siblings;
+
+ /**
+ * A doubly linked list of child directories.
+ *
+ * This attribute is protected with the global #db_mutex.
+ * Read access in the update thread does not need protection.
+ */
+ struct list_head children;
+
+ /**
+ * A doubly linked list of songs within this directory.
+ *
+ * This attribute is protected with the global #db_mutex.
+ * Read access in the update thread does not need protection.
+ */
+ struct list_head songs;
+
+ PlaylistVector playlists;
+
+ Directory *parent;
+ time_t mtime;
+ 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);
+
+public:
+ /**
+ * Default constructor, needed for #detached_root.
+ */
+ Directory();
+ ~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);
+ }
+
+ /**
+ * 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.
+ *
+ * Caller must lock the #db_mutex.
+ */
+ void Delete();
+
+ /**
+ * Create a new #Directory object as a child of the given one.
+ *
+ * Caller must lock the #db_mutex.
+ *
+ * @param name_utf8 the UTF-8 encoded name of the new sub directory
+ */
+ gcc_malloc
+ Directory *CreateChild(const char *name_utf8);
+
+ /**
+ * Caller must lock the #db_mutex.
+ */
+ gcc_pure
+ const Directory *FindChild(const char *name) const;
+
+ gcc_pure
+ Directory *FindChild(const char *name) {
+ const Directory *cthis = this;
+ return const_cast<Directory *>(cthis->FindChild(name));
+ }
+
+ /**
+ * Look up a sub directory, and create the object if it does not
+ * exist.
+ *
+ * Caller must lock the #db_mutex.
+ */
+ Directory *MakeChild(const char *name_utf8) {
+ Directory *child = FindChild(name_utf8);
+ if (child == nullptr)
+ child = CreateChild(name_utf8);
+ return child;
+ }
+
+ /**
+ * Looks up a directory by its relative URI.
+ *
+ * @param uri the relative URI
+ * @return the Directory, or NULL if none was found
+ */
+ gcc_pure
+ Directory *LookupDirectory(const char *uri);
+
+ gcc_pure
+ bool IsEmpty() const {
+ return list_empty(&children) &&
+ list_empty(&songs) &&
+ playlists.empty();
+ }
+
+ gcc_pure
+ const char *GetPath() const {
+ return path;
+ }
+
+ /**
+ * Returns the base name of the directory.
+ */
+ gcc_pure
+ const char *GetName() const;
+
+ /**
+ * Is this the root directory of the music database?
+ */
+ gcc_pure
+ bool IsRoot() const {
+ return parent == NULL;
+ }
+
+ /**
+ * Look up a song in this directory by its name.
+ *
+ * Caller must lock the #db_mutex.
+ */
+ gcc_pure
+ const Song *FindSong(const char *name_utf8) const;
+
+ gcc_pure
+ Song *FindSong(const char *name_utf8) {
+ const Directory *cthis = this;
+ return const_cast<Song *>(cthis->FindSong(name_utf8));
+ }
+
+ /**
+ * Looks up a song by its relative URI.
+ *
+ * Caller must lock the #db_mutex.
+ *
+ * @param uri the relative URI
+ * @return the song, or NULL if none was found
+ */
+ gcc_pure
+ Song *LookupSong(const char *uri);
+
+ /**
+ * Add a song object to this directory. Its "parent" attribute must
+ * be set already.
+ */
+ void AddSong(Song *song);
+
+ /**
+ * Remove a song object from this directory (which effectively
+ * invalidates the song object, because the "parent" attribute becomes
+ * stale), but does not free it.
+ */
+ void RemoveSong(Song *song);
+
+ /**
+ * Caller must lock the #db_mutex.
+ */
+ void PruneEmpty();
+
+ /**
+ * Sort all directory entries recursively.
+ *
+ * Caller must lock the #db_mutex.
+ */
+ void Sort();
+
+ /**
+ * Caller must lock #db_mutex.
+ */
+ bool Walk(bool recursive, const SongFilter *match,
+ VisitDirectory visit_directory, VisitSong visit_song,
+ VisitPlaylist visit_playlist,
+ GError **error_r) const;
+};
+
+static inline bool
+isRootDirectory(const char *name)
+{
+ return name[0] == 0 || (name[0] == '/' && name[1] == 0);
+}
+
+#endif
diff --git a/src/DirectorySave.cxx b/src/DirectorySave.cxx
new file mode 100644
index 000000000..1dcc36dbf
--- /dev/null
+++ b/src/DirectorySave.cxx
@@ -0,0 +1,180 @@
+/*
+ * 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 "DirectorySave.hxx"
+#include "Directory.hxx"
+#include "Song.hxx"
+#include "SongSave.hxx"
+#include "PlaylistDatabase.hxx"
+#include "TextFile.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+#define DIRECTORY_DIR "directory: "
+#define DIRECTORY_MTIME "mtime: "
+#define DIRECTORY_BEGIN "begin: "
+#define DIRECTORY_END "end: "
+
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+directory_quark(void)
+{
+ return g_quark_from_static_string("directory");
+}
+
+void
+directory_save(FILE *fp, const Directory *directory)
+{
+ if (!directory->IsRoot()) {
+ fprintf(fp, DIRECTORY_MTIME "%lu\n",
+ (unsigned long)directory->mtime);
+
+ fprintf(fp, "%s%s\n", DIRECTORY_BEGIN, directory->GetPath());
+ }
+
+ Directory *cur;
+ directory_for_each_child(cur, directory) {
+ char *base = g_path_get_basename(cur->path);
+
+ fprintf(fp, DIRECTORY_DIR "%s\n", base);
+ g_free(base);
+
+ directory_save(fp, cur);
+
+ if (ferror(fp))
+ return;
+ }
+
+ Song *song;
+ directory_for_each_song(song, directory)
+ song_save(fp, song);
+
+ playlist_vector_save(fp, directory->playlists);
+
+ if (!directory->IsRoot())
+ fprintf(fp, DIRECTORY_END "%s\n", directory->GetPath());
+}
+
+static Directory *
+directory_load_subdir(TextFile &file, Directory *parent, const char *name,
+ GError **error_r)
+{
+ bool success;
+
+ if (parent->FindChild(name) != nullptr) {
+ g_set_error(error_r, directory_quark(), 0,
+ "Duplicate subdirectory '%s'", name);
+ return NULL;
+ }
+
+ Directory *directory = parent->CreateChild(name);
+
+ const char *line = file.ReadLine();
+ if (line == NULL) {
+ g_set_error(error_r, directory_quark(), 0,
+ "Unexpected end of file");
+ directory->Delete();
+ return NULL;
+ }
+
+ if (g_str_has_prefix(line, DIRECTORY_MTIME)) {
+ directory->mtime =
+ g_ascii_strtoull(line + sizeof(DIRECTORY_MTIME) - 1,
+ NULL, 10);
+
+ line = file.ReadLine();
+ if (line == NULL) {
+ g_set_error(error_r, directory_quark(), 0,
+ "Unexpected end of file");
+ directory->Delete();
+ return NULL;
+ }
+ }
+
+ if (!g_str_has_prefix(line, DIRECTORY_BEGIN)) {
+ g_set_error(error_r, directory_quark(), 0,
+ "Malformed line: %s", line);
+ directory->Delete();
+ return NULL;
+ }
+
+ success = directory_load(file, directory, error_r);
+ if (!success) {
+ directory->Delete();
+ return NULL;
+ }
+
+ return directory;
+}
+
+bool
+directory_load(TextFile &file, Directory *directory, GError **error)
+{
+ const char *line;
+
+ while ((line = file.ReadLine()) != NULL &&
+ !g_str_has_prefix(line, DIRECTORY_END)) {
+ if (g_str_has_prefix(line, DIRECTORY_DIR)) {
+ Directory *subdir =
+ directory_load_subdir(file, directory,
+ line + sizeof(DIRECTORY_DIR) - 1,
+ error);
+ if (subdir == NULL)
+ return false;
+ } else if (g_str_has_prefix(line, SONG_BEGIN)) {
+ const char *name = line + sizeof(SONG_BEGIN) - 1;
+ Song *song;
+
+ if (directory->FindSong(name) != nullptr) {
+ g_set_error(error, directory_quark(), 0,
+ "Duplicate song '%s'", name);
+ return false;
+ }
+
+ song = song_load(file, directory, name, error);
+ if (song == NULL)
+ 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);
+
+ if (!playlist_metadata_load(file, directory->playlists,
+ name, error)) {
+ g_free(name);
+ return false;
+ }
+
+ g_free(name);
+ } else {
+ g_set_error(error, directory_quark(), 0,
+ "Malformed line: %s", line);
+ return false;
+ }
+ }
+
+ return true;
+}
diff --git a/src/DirectorySave.hxx b/src/DirectorySave.hxx
new file mode 100644
index 000000000..f4b4816f7
--- /dev/null
+++ b/src/DirectorySave.hxx
@@ -0,0 +1,36 @@
+/*
+ * 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_DIRECTORY_SAVE_HXX
+#define MPD_DIRECTORY_SAVE_HXX
+
+#include "gerror.h"
+
+#include <stdio.h>
+
+struct Directory;
+class TextFile;
+
+void
+directory_save(FILE *fp, const Directory *directory);
+
+bool
+directory_load(TextFile &file, Directory *directory, GError **error);
+
+#endif
diff --git a/src/EncoderAPI.hxx b/src/EncoderAPI.hxx
new file mode 100644
index 000000000..d430214d6
--- /dev/null
+++ b/src/EncoderAPI.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.
+ */
+
+/*
+ * This header is included by encoder plugins.
+ *
+ */
+
+#ifndef MPD_ENCODER_API_HXX
+#define MPD_ENCODER_API_HXX
+
+#include "EncoderPlugin.hxx"
+#include "AudioFormat.hxx"
+#include "Tag.hxx"
+#include "conf.h"
+
+#endif
diff --git a/src/EncoderList.cxx b/src/EncoderList.cxx
new file mode 100644
index 000000000..2305548b9
--- /dev/null
+++ b/src/EncoderList.cxx
@@ -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.
+ */
+
+#include "config.h"
+#include "EncoderList.hxx"
+#include "EncoderPlugin.hxx"
+#include "encoder/NullEncoderPlugin.hxx"
+#include "encoder/WaveEncoderPlugin.hxx"
+#include "encoder/VorbisEncoderPlugin.hxx"
+#include "encoder/OpusEncoderPlugin.hxx"
+#include "encoder/FlacEncoderPlugin.hxx"
+#include "encoder/LameEncoderPlugin.hxx"
+#include "encoder/TwolameEncoderPlugin.hxx"
+
+#include <string.h>
+
+const EncoderPlugin *const encoder_plugins[] = {
+ &null_encoder_plugin,
+#ifdef ENABLE_VORBIS_ENCODER
+ &vorbis_encoder_plugin,
+#endif
+#ifdef HAVE_OPUS
+ &opus_encoder_plugin,
+#endif
+#ifdef ENABLE_LAME_ENCODER
+ &lame_encoder_plugin,
+#endif
+#ifdef ENABLE_TWOLAME_ENCODER
+ &twolame_encoder_plugin,
+#endif
+#ifdef ENABLE_WAVE_ENCODER
+ &wave_encoder_plugin,
+#endif
+#ifdef ENABLE_FLAC_ENCODER
+ &flac_encoder_plugin,
+#endif
+ NULL
+};
+
+const EncoderPlugin *
+encoder_plugin_get(const char *name)
+{
+ encoder_plugins_for_each(plugin)
+ if (strcmp(plugin->name, name) == 0)
+ return plugin;
+
+ return NULL;
+}
diff --git a/src/EncoderList.hxx b/src/EncoderList.hxx
new file mode 100644
index 000000000..feaf7a6d1
--- /dev/null
+++ b/src/EncoderList.hxx
@@ -0,0 +1,43 @@
+/*
+ * 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_ENCODER_LIST_HXX
+#define MPD_ENCODER_LIST_HXX
+
+struct EncoderPlugin;
+
+extern const EncoderPlugin *const encoder_plugins[];
+
+#define encoder_plugins_for_each(plugin) \
+ for (const EncoderPlugin *plugin, \
+ *const*encoder_plugin_iterator = &encoder_plugins[0]; \
+ (plugin = *encoder_plugin_iterator) != NULL; \
+ ++encoder_plugin_iterator)
+
+/**
+ * Looks up an encoder plugin by its name.
+ *
+ * @param name the encoder name to look for
+ * @return the encoder plugin with the specified name, or NULL if none
+ * was found
+ */
+const EncoderPlugin *
+encoder_plugin_get(const char *name);
+
+#endif
diff --git a/src/EncoderPlugin.hxx b/src/EncoderPlugin.hxx
new file mode 100644
index 000000000..6bb2e1583
--- /dev/null
+++ b/src/EncoderPlugin.hxx
@@ -0,0 +1,328 @@
+/*
+ * 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_ENCODER_PLUGIN_HXX
+#define MPD_ENCODER_PLUGIN_HXX
+
+#include "gerror.h"
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+struct EncoderPlugin;
+struct AudioFormat;
+struct config_param;
+struct Tag;
+
+struct Encoder {
+ const EncoderPlugin &plugin;
+
+#ifndef NDEBUG
+ bool open, pre_tag, tag, end;
+#endif
+
+ explicit Encoder(const EncoderPlugin &_plugin)
+ :plugin(_plugin)
+#ifndef NDEBUG
+ , open(false)
+#endif
+ {}
+};
+
+struct EncoderPlugin {
+ const char *name;
+
+ Encoder *(*init)(const config_param &param,
+ GError **error);
+
+ void (*finish)(Encoder *encoder);
+
+ bool (*open)(Encoder *encoder,
+ AudioFormat &audio_format,
+ GError **error);
+
+ void (*close)(Encoder *encoder);
+
+ bool (*end)(Encoder *encoder, GError **error);
+
+ bool (*flush)(Encoder *encoder, GError **error);
+
+ bool (*pre_tag)(Encoder *encoder, GError **error);
+
+ bool (*tag)(Encoder *encoder, const Tag *tag,
+ GError **error);
+
+ bool (*write)(Encoder *encoder,
+ const void *data, size_t length,
+ GError **error);
+
+ size_t (*read)(Encoder *encoder, void *dest, size_t length);
+
+ const char *(*get_mime_type)(Encoder *encoder);
+};
+
+/**
+ * Creates a new encoder object.
+ *
+ * @param plugin the encoder plugin
+ * @param param optional configuration
+ * @param error location to store the error occurring, or NULL to ignore errors.
+ * @return an encoder object on success, NULL on failure
+ */
+static inline Encoder *
+encoder_init(const EncoderPlugin &plugin, const config_param &param,
+ GError **error_r)
+{
+ return plugin.init(param, error_r);
+}
+
+/**
+ * Frees an encoder object.
+ *
+ * @param encoder the encoder
+ */
+static inline void
+encoder_finish(Encoder *encoder)
+{
+ assert(!encoder->open);
+
+ encoder->plugin.finish(encoder);
+}
+
+/**
+ * Opens an encoder object. You must call this prior to using it.
+ * Before you free it, you must call encoder_close(). You may open
+ * and close (reuse) one encoder any number of times.
+ *
+ * After this function returns successfully and before the first
+ * encoder_write() call, you should invoke encoder_read() to obtain
+ * the file header.
+ *
+ * @param encoder the encoder
+ * @param audio_format the encoder's input audio format; the plugin
+ * may modify the struct to adapt it to its abilities
+ * @param error location to store the error occurring, or NULL to ignore errors.
+ * @return true on success
+ */
+static inline bool
+encoder_open(Encoder *encoder, AudioFormat &audio_format,
+ GError **error)
+{
+ assert(!encoder->open);
+
+ bool success = encoder->plugin.open(encoder, audio_format, error);
+#ifndef NDEBUG
+ encoder->open = success;
+ encoder->pre_tag = encoder->tag = encoder->end = false;
+#endif
+ return success;
+}
+
+/**
+ * Closes an encoder object. This disables the encoder, and readies
+ * it for reusal by calling encoder_open() again.
+ *
+ * @param encoder the encoder
+ */
+static inline void
+encoder_close(Encoder *encoder)
+{
+ assert(encoder->open);
+
+ if (encoder->plugin.close != NULL)
+ encoder->plugin.close(encoder);
+
+#ifndef NDEBUG
+ encoder->open = false;
+#endif
+}
+
+/**
+ * Ends the stream: flushes the encoder object, generate an
+ * end-of-stream marker (if applicable), make everything which might
+ * currently be buffered available by encoder_read().
+ *
+ * After this function has been called, the encoder may not be usable
+ * for more data, and only encoder_read() and encoder_close() can be
+ * called.
+ *
+ * @param encoder the encoder
+ * @param error location to store the error occuring, or NULL to ignore errors.
+ * @return true on success
+ */
+static inline bool
+encoder_end(Encoder *encoder, GError **error)
+{
+ assert(encoder->open);
+ assert(!encoder->end);
+
+#ifndef NDEBUG
+ encoder->end = true;
+#endif
+
+ /* this method is optional */
+ return encoder->plugin.end != NULL
+ ? encoder->plugin.end(encoder, error)
+ : true;
+}
+
+/**
+ * Flushes an encoder object, make everything which might currently be
+ * buffered available by encoder_read().
+ *
+ * @param encoder the encoder
+ * @param error location to store the error occurring, or NULL to ignore errors.
+ * @return true on success
+ */
+static inline bool
+encoder_flush(Encoder *encoder, GError **error)
+{
+ assert(encoder->open);
+ assert(!encoder->pre_tag);
+ assert(!encoder->tag);
+ assert(!encoder->end);
+
+ /* this method is optional */
+ return encoder->plugin.flush != NULL
+ ? encoder->plugin.flush(encoder, error)
+ : true;
+}
+
+/**
+ * Prepare for sending a tag to the encoder. This is used by some
+ * encoders to flush the previous sub-stream, in preparation to begin
+ * a new one.
+ *
+ * @param encoder the encoder
+ * @param tag the tag object
+ * @param error location to store the error occuring, or NULL to ignore errors.
+ * @return true on success
+ */
+static inline bool
+encoder_pre_tag(Encoder *encoder, GError **error)
+{
+ assert(encoder->open);
+ assert(!encoder->pre_tag);
+ assert(!encoder->tag);
+ assert(!encoder->end);
+
+ /* this method is optional */
+ bool success = encoder->plugin.pre_tag != NULL
+ ? encoder->plugin.pre_tag(encoder, error)
+ : true;
+
+#ifndef NDEBUG
+ encoder->pre_tag = success;
+#endif
+ return success;
+}
+
+/**
+ * Sends a tag to the encoder.
+ *
+ * Instructions: call encoder_pre_tag(); then obtain flushed data with
+ * encoder_read(); finally call encoder_tag().
+ *
+ * @param encoder the encoder
+ * @param tag the tag object
+ * @param error location to store the error occurring, or NULL to ignore errors.
+ * @return true on success
+ */
+static inline bool
+encoder_tag(Encoder *encoder, const Tag *tag, GError **error)
+{
+ assert(encoder->open);
+ assert(!encoder->pre_tag);
+ assert(encoder->tag);
+ assert(!encoder->end);
+
+#ifndef NDEBUG
+ encoder->tag = false;
+#endif
+
+ /* this method is optional */
+ return encoder->plugin.tag != NULL
+ ? encoder->plugin.tag(encoder, tag, error)
+ : true;
+}
+
+/**
+ * Writes raw PCM data to the encoder.
+ *
+ * @param encoder the encoder
+ * @param data the buffer containing PCM samples
+ * @param length the length of the buffer in bytes
+ * @param error location to store the error occurring, or NULL to ignore errors.
+ * @return true on success
+ */
+static inline bool
+encoder_write(Encoder *encoder, const void *data, size_t length,
+ GError **error)
+{
+ assert(encoder->open);
+ assert(!encoder->pre_tag);
+ assert(!encoder->tag);
+ assert(!encoder->end);
+
+ return encoder->plugin.write(encoder, data, length, error);
+}
+
+/**
+ * Reads encoded data from the encoder.
+ *
+ * Call this repeatedly until no more data is returned.
+ *
+ * @param encoder the encoder
+ * @param dest the destination buffer to copy to
+ * @param length the maximum length of the destination buffer
+ * @return the number of bytes written to #dest
+ */
+static inline size_t
+encoder_read(Encoder *encoder, void *dest, size_t length)
+{
+ assert(encoder->open);
+ assert(!encoder->pre_tag || !encoder->tag);
+
+#ifndef NDEBUG
+ if (encoder->pre_tag) {
+ encoder->pre_tag = false;
+ encoder->tag = true;
+ }
+#endif
+
+ return encoder->plugin.read(encoder, dest, length);
+}
+
+/**
+ * Get mime type of encoded content.
+ *
+ * @param plugin the encoder plugin
+ * @return an constant string, NULL on failure
+ */
+static inline const char *
+encoder_get_mime_type(Encoder *encoder)
+{
+ /* this method is optional */
+ return encoder->plugin.get_mime_type != NULL
+ ? encoder->plugin.get_mime_type(encoder)
+ : NULL;
+}
+
+#endif
diff --git a/src/ExcludeList.cxx b/src/ExcludeList.cxx
new file mode 100644
index 000000000..ed04b8d38
--- /dev/null
+++ b/src/ExcludeList.cxx
@@ -0,0 +1,77 @@
+/*
+ * 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.
+ */
+
+/*
+ * The .mpdignore backend code.
+ *
+ */
+
+#include "config.h"
+#include "ExcludeList.hxx"
+#include "fs/Path.hxx"
+#include "fs/FileSystem.hxx"
+
+#include <assert.h>
+#include <string.h>
+#include <errno.h>
+
+bool
+ExcludeList::LoadFile(const Path &path_fs)
+{
+ FILE *file = FOpen(path_fs, FOpenMode::ReadText);
+ if (file == NULL) {
+ if (errno != ENOENT) {
+ const char *msg = g_strerror(errno);
+ const auto path_utf8 = path_fs.ToUTF8();
+ g_debug("Failed to open %s: %s",
+ path_utf8.c_str(), msg);
+ }
+
+ return false;
+ }
+
+ char line[1024];
+ while (fgets(line, sizeof(line), file) != NULL) {
+ char *p = strchr(line, '#');
+ if (p != NULL)
+ *p = 0;
+
+ p = g_strstrip(line);
+ if (*p != 0)
+ patterns.emplace_front(p);
+ }
+
+ fclose(file);
+
+ return true;
+}
+
+bool
+ExcludeList::Check(const Path &name_fs) const
+{
+ assert(!name_fs.IsNull());
+
+ /* XXX include full path name in check */
+
+ for (const auto &i : patterns)
+ if (i.Check(name_fs.c_str()))
+ return true;
+
+ return false;
+}
diff --git a/src/ExcludeList.hxx b/src/ExcludeList.hxx
new file mode 100644
index 000000000..7111465a3
--- /dev/null
+++ b/src/ExcludeList.hxx
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+
+/*
+ * The .mpdignore backend code.
+ *
+ */
+
+#ifndef MPD_EXCLUDE_H
+#define MPD_EXCLUDE_H
+
+#include "gcc.h"
+
+#include <forward_list>
+
+#include <glib.h>
+
+class Path;
+
+class ExcludeList {
+ class Pattern {
+ GPatternSpec *pattern;
+
+ public:
+ Pattern(const char *_pattern)
+ :pattern(g_pattern_spec_new(_pattern)) {}
+
+ Pattern(Pattern &&other)
+ :pattern(other.pattern) {
+ other.pattern = nullptr;
+ }
+
+ ~Pattern() {
+ g_pattern_spec_free(pattern);
+ }
+
+ gcc_pure
+ bool Check(const char *name_fs) const {
+ return g_pattern_match_string(pattern, name_fs);
+ }
+ };
+
+ std::forward_list<Pattern> patterns;
+
+public:
+ gcc_pure
+ bool IsEmpty() const {
+ return patterns.empty();
+ }
+
+ /**
+ * Loads and parses a .mpdignore file.
+ */
+ bool LoadFile(const Path &path_fs);
+
+ /**
+ * Checks whether one of the patterns in the .mpdignore file matches
+ * the specified file name.
+ */
+ bool Check(const Path &name_fs) const;
+};
+
+
+#endif
diff --git a/src/FilterConfig.cxx b/src/FilterConfig.cxx
new file mode 100644
index 000000000..389afa99d
--- /dev/null
+++ b/src/FilterConfig.cxx
@@ -0,0 +1,120 @@
+/*
+ * 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 "FilterConfig.hxx"
+#include "conf.h"
+#include "filter/ChainFilterPlugin.hxx"
+#include "FilterPlugin.hxx"
+#include "FilterInternal.hxx"
+#include "FilterRegistry.hxx"
+
+#include <glib.h>
+
+#include <string.h>
+
+static GQuark
+filter_quark(void)
+{
+ return g_quark_from_static_string("filter");
+}
+
+/**
+ * Find the "filter" configuration block for the specified name.
+ *
+ * @param filter_template_name the name of the filter template
+ * @param error_r space to return an error description
+ * @return the configuration block, or NULL if none was configured
+ */
+static const struct config_param *
+filter_plugin_config(const char *filter_template_name, GError **error_r)
+{
+ const struct config_param *param = NULL;
+
+ while ((param = config_get_next_param(CONF_AUDIO_FILTER, param)) != NULL) {
+ const char *name = param->GetBlockValue("name");
+ if (name == NULL) {
+ g_set_error(error_r, filter_quark(), 1,
+ "filter configuration without 'name' name in line %d",
+ param->line);
+ return NULL;
+ }
+
+ if (strcmp(name, filter_template_name) == 0)
+ return param;
+ }
+
+ g_set_error(error_r, filter_quark(), 1,
+ "filter template not found: %s",
+ filter_template_name);
+
+ return NULL;
+}
+
+/**
+ * Builds a filter chain from a configuration string on the form
+ * "name1, name2, name3, ..." by looking up each name among the
+ * configured filter sections.
+ * @param chain the chain to append filters on
+ * @param spec the filter chain specification
+ * @param error_r space to return an error description
+ * @return the number of filters which were successfully added
+ */
+unsigned int
+filter_chain_parse(Filter &chain, const char *spec, GError **error_r)
+{
+
+ // Split on comma
+ gchar** tokens = g_strsplit_set(spec, ",", 255);
+
+ unsigned added_filters = 0;
+
+ // Add each name to the filter chain by instantiating an actual filter
+ char **template_names = tokens;
+ while (*template_names != NULL) {
+ // Squeeze whitespace
+ g_strstrip(*template_names);
+
+ const struct config_param *cfg =
+ filter_plugin_config(*template_names, error_r);
+ if (cfg == NULL) {
+ // The error has already been set, just stop.
+ break;
+ }
+
+ // Instantiate one of those filter plugins with the template name as a hint
+ Filter *f = filter_configured_new(*cfg, error_r);
+ if (f == NULL) {
+ // The error has already been set, just stop.
+ break;
+ }
+
+ const char *plugin_name = cfg->GetBlockValue("plugin",
+ "unknown");
+
+ filter_chain_append(chain, plugin_name, f);
+ ++added_filters;
+
+ ++template_names;
+ }
+
+ g_strfreev(tokens);
+
+ return added_filters;
+}
diff --git a/src/FilterConfig.hxx b/src/FilterConfig.hxx
new file mode 100644
index 000000000..bad186354
--- /dev/null
+++ b/src/FilterConfig.hxx
@@ -0,0 +1,44 @@
+/*
+ * 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
+ *
+ * Utility functions for filter configuration
+ */
+
+#ifndef MPD_FILTER_CONFIG_HXX
+#define MPD_FILTER_CONFIG_HXX
+
+#include "gerror.h"
+
+class Filter;
+
+/**
+ * Builds a filter chain from a configuration string on the form
+ * "name1, name2, name3, ..." by looking up each name among the
+ * configured filter sections.
+ * @param chain the chain to append filters on
+ * @param spec the filter chain specification
+ * @param error_r space to return an error description
+ * @return the number of filters which were successfully added
+ */
+unsigned int
+filter_chain_parse(Filter &chain, const char *spec, GError **error_r);
+
+#endif
diff --git a/src/FilterInternal.hxx b/src/FilterInternal.hxx
new file mode 100644
index 000000000..103687b61
--- /dev/null
+++ b/src/FilterInternal.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.
+ */
+
+/** \file
+ *
+ * Internal stuff for the filter core and filter plugins.
+ */
+
+#ifndef MPD_FILTER_INTERNAL_HXX
+#define MPD_FILTER_INTERNAL_HXX
+
+struct AudioFormat;
+
+class Filter {
+public:
+ virtual ~Filter() {}
+
+ /**
+ * Opens the filter, preparing it for FilterPCM().
+ *
+ * @param filter the filter object
+ * @param audio_format the audio format of incoming data; the
+ * plugin may modify the object to enforce another input
+ * format
+ * @param error location to store the error occurring, or NULL
+ * to ignore errors.
+ * @return the format of outgoing data or
+ * AudioFormat::Undefined() on error
+ */
+ virtual AudioFormat Open(AudioFormat &af, GError **error_r) = 0;
+
+ /**
+ * Closes the filter. After that, you may call Open() again.
+ */
+ virtual void Close() = 0;
+
+ /**
+ * Filters a block of PCM data.
+ *
+ * @param filter the filter object
+ * @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 NULL
+ * to ignore errors.
+ * @return the destination buffer on success (will be
+ * invalidated by filter_close() or filter_filter()), NULL on
+ * error
+ */
+ virtual const void *FilterPCM(const void *src, size_t src_size,
+ size_t *dest_size_r,
+ GError **error_r) = 0;
+};
+
+#endif
diff --git a/src/FilterPlugin.cxx b/src/FilterPlugin.cxx
new file mode 100644
index 000000000..076056fbe
--- /dev/null
+++ b/src/FilterPlugin.cxx
@@ -0,0 +1,61 @@
+/*
+ * 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 "FilterPlugin.hxx"
+#include "FilterInternal.hxx"
+#include "FilterRegistry.hxx"
+#include "conf.h"
+#include "ConfigQuark.hxx"
+
+#include <assert.h>
+
+Filter *
+filter_new(const struct filter_plugin *plugin,
+ const config_param &param, GError **error_r)
+{
+ assert(plugin != NULL);
+ assert(error_r == NULL || *error_r == NULL);
+
+ return plugin->init(param, error_r);
+}
+
+Filter *
+filter_configured_new(const config_param &param, GError **error_r)
+{
+ const struct filter_plugin *plugin;
+
+ assert(error_r == NULL || *error_r == NULL);
+
+ const char *plugin_name = param.GetBlockValue("plugin");
+ if (plugin_name == NULL) {
+ g_set_error(error_r, config_quark(), 0,
+ "No filter plugin specified");
+ return NULL;
+ }
+
+ plugin = filter_plugin_by_name(plugin_name);
+ if (plugin == NULL) {
+ g_set_error(error_r, config_quark(), 0,
+ "No such filter plugin: %s", plugin_name);
+ return NULL;
+ }
+
+ return filter_new(plugin, param, error_r);
+}
diff --git a/src/FilterPlugin.hxx b/src/FilterPlugin.hxx
new file mode 100644
index 000000000..af1e2f699
--- /dev/null
+++ b/src/FilterPlugin.hxx
@@ -0,0 +1,70 @@
+/*
+ * 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
+ *
+ * This header declares the filter_plugin class. It describes a
+ * plugin API for objects which filter raw PCM data.
+ */
+
+#ifndef MPD_FILTER_PLUGIN_HXX
+#define MPD_FILTER_PLUGIN_HXX
+
+#include "gerror.h"
+
+#include <stddef.h>
+
+struct config_param;
+class Filter;
+
+struct filter_plugin {
+ const char *name;
+
+ /**
+ * Allocates and configures a filter.
+ */
+ Filter *(*init)(const config_param &param, GError **error_r);
+};
+
+/**
+ * Creates a new instance of the specified filter plugin.
+ *
+ * @param plugin the filter plugin
+ * @param param optional configuration section
+ * @param error location to store the error occurring, or NULL to
+ * ignore errors.
+ * @return a new filter object, or NULL on error
+ */
+Filter *
+filter_new(const struct filter_plugin *plugin,
+ const config_param &param, GError **error_r);
+
+/**
+ * Creates a new filter, loads configuration and the plugin name from
+ * the specified configuration section.
+ *
+ * @param param the configuration section
+ * @param error location to store the error occurring, or NULL to
+ * ignore errors.
+ * @return a new filter object, or NULL on error
+ */
+Filter *
+filter_configured_new(const config_param &param, GError **error_r);
+
+#endif
diff --git a/src/FilterRegistry.cxx b/src/FilterRegistry.cxx
new file mode 100644
index 000000000..c8aff8298
--- /dev/null
+++ b/src/FilterRegistry.cxx
@@ -0,0 +1,44 @@
+/*
+ * 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 "FilterRegistry.hxx"
+#include "FilterPlugin.hxx"
+
+#include <stddef.h>
+#include <string.h>
+
+const struct filter_plugin *const filter_plugins[] = {
+ &null_filter_plugin,
+ &route_filter_plugin,
+ &normalize_filter_plugin,
+ &volume_filter_plugin,
+ &replay_gain_filter_plugin,
+ NULL,
+};
+
+const struct filter_plugin *
+filter_plugin_by_name(const char *name)
+{
+ for (unsigned i = 0; filter_plugins[i] != NULL; ++i)
+ if (strcmp(filter_plugins[i]->name, name) == 0)
+ return filter_plugins[i];
+
+ return NULL;
+}
diff --git a/src/FilterRegistry.hxx b/src/FilterRegistry.hxx
new file mode 100644
index 000000000..c21d8a597
--- /dev/null
+++ b/src/FilterRegistry.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.
+ */
+
+/** \file
+ *
+ * This library manages all filter plugins which are enabled at
+ * compile time.
+ */
+
+#ifndef MPD_FILTER_REGISTRY_HXX
+#define MPD_FILTER_REGISTRY_HXX
+
+extern const struct filter_plugin null_filter_plugin;
+extern const struct filter_plugin chain_filter_plugin;
+extern const struct filter_plugin convert_filter_plugin;
+extern const struct filter_plugin route_filter_plugin;
+extern const struct filter_plugin normalize_filter_plugin;
+extern const struct filter_plugin volume_filter_plugin;
+extern const struct filter_plugin replay_gain_filter_plugin;
+
+const struct filter_plugin *
+filter_plugin_by_name(const char *name);
+
+#endif
diff --git a/src/GlobalEvents.cxx b/src/GlobalEvents.cxx
new file mode 100644
index 000000000..20faa9a81
--- /dev/null
+++ b/src/GlobalEvents.cxx
@@ -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.
+ */
+
+#include "config.h"
+#include "GlobalEvents.hxx"
+
+#include <atomic>
+
+#include <assert.h>
+#include <glib.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "global_events"
+
+namespace GlobalEvents {
+ static guint source_id;
+ static std::atomic_uint flags;
+ static Handler handlers[MAX];
+}
+
+/**
+ * Invoke the callback for a certain event.
+ */
+static void
+InvokeGlobalEvent(GlobalEvents::Event event)
+{
+ assert((unsigned)event < GlobalEvents::MAX);
+ assert(GlobalEvents::handlers[event] != NULL);
+
+ GlobalEvents::handlers[event]();
+}
+
+static gboolean
+GlobalEventCallback(G_GNUC_UNUSED gpointer data)
+{
+ const unsigned flags = GlobalEvents::flags.exchange(0);
+
+ for (unsigned i = 0; i < GlobalEvents::MAX; ++i)
+ if (flags & (1u << i))
+ /* invoke the event handler */
+ InvokeGlobalEvent(GlobalEvents::Event(i));
+
+ return false;
+}
+
+void
+GlobalEvents::Initialize()
+{
+}
+
+void
+GlobalEvents::Deinitialize()
+{
+ if (source_id != 0)
+ g_source_remove(source_id);
+}
+
+void
+GlobalEvents::Register(Event event, Handler callback)
+{
+ assert((unsigned)event < MAX);
+ assert(handlers[event] == NULL);
+
+ handlers[event] = callback;
+}
+
+void
+GlobalEvents::Emit(Event event)
+{
+ assert((unsigned)event < MAX);
+
+ const unsigned mask = 1u << unsigned(event);
+ if (GlobalEvents::flags.fetch_or(mask) == 0)
+ source_id = g_idle_add(GlobalEventCallback, nullptr);
+}
diff --git a/src/GlobalEvents.hxx b/src/GlobalEvents.hxx
new file mode 100644
index 000000000..fac116935
--- /dev/null
+++ b/src/GlobalEvents.hxx
@@ -0,0 +1,72 @@
+/*
+ * 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_GLOBAL_EVENTS_HXX
+#define MPD_GLOBAL_EVENTS_HXX
+
+#ifdef WIN32
+#include <windows.h>
+/* DELETE is a WIN32 macro that poisons our namespace; this is a
+ kludge to allow us to use it anyway */
+#ifdef DELETE
+#undef DELETE
+#endif
+#endif
+
+namespace GlobalEvents {
+ enum Event {
+ /** database update was finished */
+ UPDATE,
+
+ /** during database update, a song was deleted */
+ DELETE,
+
+ /** an idle event was emitted */
+ IDLE,
+
+ /** must call playlist_sync() */
+ PLAYLIST,
+
+ /** the current song's tag has changed */
+ TAG,
+
+ /** SIGHUP received: reload configuration, roll log file */
+ RELOAD,
+
+ /** a hardware mixer plugin has detected a change */
+ MIXER,
+
+ /** shutdown requested */
+ SHUTDOWN,
+
+ MAX
+ };
+
+ typedef void (*Handler)();
+
+ void Initialize();
+
+ void Deinitialize();
+
+ void Register(Event event, Handler handler);
+
+ void Emit(Event event);
+}
+
+#endif /* MAIN_NOTIFY_H */
diff --git a/src/IOThread.cxx b/src/IOThread.cxx
new file mode 100644
index 000000000..bbd4b9c3c
--- /dev/null
+++ b/src/IOThread.cxx
@@ -0,0 +1,167 @@
+/*
+ * 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 "IOThread.hxx"
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+#include "event/Loop.hxx"
+
+#include <assert.h>
+
+static struct {
+ Mutex mutex;
+ Cond cond;
+
+ EventLoop *loop;
+ GThread *thread;
+} io;
+
+void
+io_thread_run(void)
+{
+ assert(io_thread_inside());
+ assert(io.loop != NULL);
+
+ io.loop->Run();
+}
+
+static gpointer
+io_thread_func(G_GNUC_UNUSED gpointer arg)
+{
+ /* lock+unlock to synchronize with io_thread_start(), to be
+ sure that io.thread is set */
+ io.mutex.lock();
+ io.mutex.unlock();
+
+ io_thread_run();
+ return NULL;
+}
+
+void
+io_thread_init(void)
+{
+ assert(io.loop == NULL);
+ assert(io.thread == NULL);
+
+ io.loop = new EventLoop();
+}
+
+bool
+io_thread_start(gcc_unused GError **error_r)
+{
+ assert(io.loop != NULL);
+ assert(io.thread == NULL);
+
+ const ScopeLock protect(io.mutex);
+
+#if GLIB_CHECK_VERSION(2,32,0)
+ io.thread = g_thread_new("io", io_thread_func, nullptr);
+#else
+ io.thread = g_thread_create(io_thread_func, NULL, true, error_r);
+ if (io.thread == NULL)
+ return false;
+#endif
+
+ return true;
+}
+
+void
+io_thread_quit(void)
+{
+ assert(io.loop != NULL);
+
+ io.loop->Break();
+}
+
+void
+io_thread_deinit(void)
+{
+ if (io.thread != NULL) {
+ io_thread_quit();
+
+ g_thread_join(io.thread);
+ }
+
+ delete io.loop;
+}
+
+EventLoop &
+io_thread_get()
+{
+ assert(io.loop != nullptr);
+
+ return *io.loop;
+}
+
+bool
+io_thread_inside(void)
+{
+ return io.thread != NULL && g_thread_self() == io.thread;
+}
+
+struct call_data {
+ GThreadFunc function;
+ gpointer data;
+ bool done;
+ gpointer result;
+};
+
+static gboolean
+io_thread_call_func(gpointer _data)
+{
+ struct call_data *data = (struct call_data *)_data;
+
+ gpointer result = data->function(data->data);
+
+ io.mutex.lock();
+ data->done = true;
+ data->result = result;
+ io.cond.broadcast();
+ io.mutex.unlock();
+
+ return false;
+}
+
+gpointer
+io_thread_call(GThreadFunc function, gpointer _data)
+{
+ assert(io.thread != NULL);
+
+ if (io_thread_inside())
+ /* we're already in the I/O thread - no
+ synchronization needed */
+ return function(_data);
+
+ struct call_data data = {
+ function,
+ _data,
+ false,
+ nullptr,
+ };
+
+ io.loop->AddIdle(io_thread_call_func, &data);
+
+ io.mutex.lock();
+ while (!data.done)
+ io.cond.wait(io.mutex);
+ io.mutex.unlock();
+
+ return data.result;
+}
diff --git a/src/IOThread.hxx b/src/IOThread.hxx
new file mode 100644
index 000000000..a9401dc7f
--- /dev/null
+++ b/src/IOThread.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_IO_THREAD_HXX
+#define MPD_IO_THREAD_HXX
+
+#include "gcc.h"
+
+#include <glib.h>
+
+class EventLoop;
+
+void
+io_thread_init(void);
+
+bool
+io_thread_start(GError **error_r);
+
+/**
+ * Run the I/O event loop synchronously in the current thread. This
+ * can be called instead of io_thread_start(). For testing purposes
+ * only.
+ */
+void
+io_thread_run(void);
+
+/**
+ * Ask the I/O thread to quit, but does not wait for it. Usually, you
+ * don't need to call this function, because io_thread_deinit()
+ * includes this.
+ */
+void
+io_thread_quit(void);
+
+void
+io_thread_deinit(void);
+
+gcc_pure
+EventLoop &
+io_thread_get();
+
+/**
+ * Is the current thread the I/O thread?
+ */
+gcc_pure
+bool
+io_thread_inside(void);
+
+/**
+ * Call a function synchronously in the I/O thread.
+ */
+gpointer
+io_thread_call(GThreadFunc function, gpointer data);
+
+#endif
diff --git a/src/IcyMetaDataParser.cxx b/src/IcyMetaDataParser.cxx
new file mode 100644
index 000000000..6e1e18a51
--- /dev/null
+++ b/src/IcyMetaDataParser.cxx
@@ -0,0 +1,171 @@
+/*
+ * 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 "IcyMetaDataParser.hxx"
+#include "Tag.hxx"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "icy_metadata"
+
+void
+IcyMetaDataParser::Reset()
+{
+ if (!IsDefined())
+ return;
+
+ if (data_rest == 0 && meta_size > 0)
+ g_free(meta_data);
+
+ delete tag;
+
+ data_rest = data_size;
+ meta_size = 0;
+}
+
+size_t
+IcyMetaDataParser::Data(size_t length)
+{
+ assert(length > 0);
+
+ if (!IsDefined())
+ return length;
+
+ if (data_rest == 0)
+ return 0;
+
+ if (length >= data_rest) {
+ length = data_rest;
+ data_rest = 0;
+ } else
+ data_rest -= length;
+
+ return length;
+}
+
+static void
+icy_add_item(Tag &tag, enum tag_type type, const char *value)
+{
+ size_t length = strlen(value);
+
+ if (length >= 2 && value[0] == '\'' && value[length - 1] == '\'') {
+ /* strip the single quotes */
+ ++value;
+ length -= 2;
+ }
+
+ if (length > 0)
+ tag.AddItem(type, value, length);
+}
+
+static void
+icy_parse_tag_item(Tag &tag, const char *item)
+{
+ gchar **p = g_strsplit(item, "=", 0);
+
+ if (p[0] != nullptr && p[1] != nullptr) {
+ if (strcmp(p[0], "StreamTitle") == 0)
+ icy_add_item(tag, TAG_TITLE, p[1]);
+ else
+ g_debug("unknown icy-tag: '%s'", p[0]);
+ }
+
+ g_strfreev(p);
+}
+
+static Tag *
+icy_parse_tag(const char *p)
+{
+ Tag *tag = new Tag();
+ gchar **items = g_strsplit(p, ";", 0);
+
+ for (unsigned i = 0; items[i] != nullptr; ++i)
+ icy_parse_tag_item(*tag, items[i]);
+
+ g_strfreev(items);
+
+ return tag;
+}
+
+size_t
+IcyMetaDataParser::Meta(const void *data, size_t length)
+{
+ const unsigned char *p = (const unsigned char *)data;
+
+ assert(IsDefined());
+ assert(data_rest == 0);
+ assert(length > 0);
+
+ if (meta_size == 0) {
+ /* read meta_size from the first byte of a meta
+ block */
+ meta_size = *p++ * 16;
+ if (meta_size == 0) {
+ /* special case: no metadata */
+ data_rest = data_size;
+ return 1;
+ }
+
+ /* 1 byte was consumed (must be re-added later for the
+ return value */
+ --length;
+
+ /* initialize metadata reader, allocate enough
+ memory (+1 for the null terminator) */
+ meta_position = 0;
+ meta_data = (char *)g_malloc(meta_size + 1);
+ }
+
+ assert(meta_position < meta_size);
+
+ if (length > meta_size - meta_position)
+ length = meta_size - meta_position;
+
+ memcpy(meta_data + meta_position, p, length);
+ meta_position += length;
+
+ if (p != data)
+ /* re-add the first byte (which contained meta_size) */
+ ++length;
+
+ if (meta_position == meta_size) {
+ /* null-terminate the string */
+
+ meta_data[meta_size] = 0;
+
+ /* parse */
+
+ delete tag;
+
+ tag = icy_parse_tag(meta_data);
+ g_free(meta_data);
+
+ /* change back to normal data mode */
+
+ meta_size = 0;
+ data_rest = data_size;
+ }
+
+ return length;
+}
diff --git a/src/IcyMetaDataParser.hxx b/src/IcyMetaDataParser.hxx
new file mode 100644
index 000000000..6bcb09668
--- /dev/null
+++ b/src/IcyMetaDataParser.hxx
@@ -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.
+ */
+
+#ifndef MPD_ICY_META_DATA_PARSER_HXX
+#define MPD_ICY_META_DATA_PARSER_HXX
+
+#include <stddef.h>
+
+struct Tag;
+
+class IcyMetaDataParser {
+ size_t data_size, data_rest;
+
+ size_t meta_size, meta_position;
+ char *meta_data;
+
+ Tag *tag;
+
+public:
+ IcyMetaDataParser():data_size(0) {}
+ ~IcyMetaDataParser() {
+ Reset();
+ }
+
+ /**
+ * Initialize an enabled icy_metadata object with the specified
+ * data_size (from the icy-metaint HTTP response header).
+ */
+ void Start(size_t _data_size) {
+ data_size = data_rest = _data_size;
+ meta_size = 0;
+ tag = nullptr;
+ }
+
+ /**
+ * Resets the icy_metadata. Call this after rewinding the stream.
+ */
+ void Reset();
+
+ /**
+ * Checks whether the icy_metadata object is enabled.
+ */
+ bool IsDefined() const {
+ return data_size > 0;
+ }
+
+ /**
+ * Evaluates data. Returns the number of bytes of normal data which
+ * can be read by the caller, but not more than "length". If the
+ * return value is smaller than "length", the caller should invoke
+ * icy_meta().
+ */
+ size_t Data(size_t length);
+
+ /**
+ * Reads metadata from the stream. Returns the number of bytes
+ * consumed. If the return value is smaller than "length", the caller
+ * should invoke icy_data().
+ */
+ size_t Meta(const void *data, size_t length);
+
+ Tag *ReadTag() {
+ Tag *result = tag;
+ tag = nullptr;
+ return result;
+ }
+};
+
+#endif
diff --git a/src/IcyMetaDataServer.cxx b/src/IcyMetaDataServer.cxx
new file mode 100644
index 000000000..d0ae0b77a
--- /dev/null
+++ b/src/IcyMetaDataServer.cxx
@@ -0,0 +1,139 @@
+/*
+ * 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 "IcyMetaDataServer.hxx"
+#include "Page.hxx"
+#include "Tag.hxx"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "icy_server"
+
+char*
+icy_server_metadata_header(const char *name,
+ const char *genre, const char *url,
+ const char *content_type, int metaint)
+{
+ return g_strdup_printf("ICY 200 OK\r\n"
+ "icy-notice1:<BR>This stream requires an audio player!<BR>\r\n" /* TODO */
+ "icy-notice2:MPD - The music player daemon<BR>\r\n"
+ "icy-name: %s\r\n" /* TODO */
+ "icy-genre: %s\r\n" /* TODO */
+ "icy-url: %s\r\n" /* TODO */
+ "icy-pub:1\r\n"
+ "icy-metaint:%d\r\n"
+ /* TODO "icy-br:%d\r\n" */
+ "Content-Type: %s\r\n"
+ "Connection: close\r\n"
+ "Pragma: no-cache\r\n"
+ "Cache-Control: no-cache, no-store\r\n"
+ "\r\n",
+ name,
+ genre,
+ url,
+ metaint,
+ /* bitrate, */
+ content_type);
+}
+
+static char *
+icy_server_metadata_string(const char *stream_title, const char* stream_url)
+{
+ gchar *icy_metadata;
+ guint meta_length;
+
+ // The leading n is a placeholder for the length information
+ icy_metadata = g_strdup_printf("nStreamTitle='%s';"
+ "StreamUrl='%s';",
+ stream_title,
+ stream_url);
+
+ g_return_val_if_fail(icy_metadata, NULL);
+
+ meta_length = strlen(icy_metadata);
+
+ meta_length--; // subtract placeholder
+
+ meta_length = ((int)meta_length / 16) + 1;
+
+ icy_metadata[0] = meta_length;
+
+ if (meta_length > 255) {
+ g_free(icy_metadata);
+ return NULL;
+ }
+
+ return icy_metadata;
+}
+
+Page *
+icy_server_metadata_page(const Tag &tag, const enum tag_type *types)
+{
+ const gchar *tag_items[TAG_NUM_OF_ITEM_TYPES];
+ gint last_item, item;
+ guint position;
+ gchar *icy_string;
+ gchar stream_title[(1 + 255 - 28) * 16]; // Length + Metadata -
+ // "StreamTitle='';StreamUrl='';"
+ // = 4081 - 28
+ stream_title[0] = '\0';
+
+ last_item = -1;
+
+ while (*types != TAG_NUM_OF_ITEM_TYPES) {
+ const gchar *tag_item = tag.GetValue(*types++);
+ if (tag_item)
+ tag_items[++last_item] = tag_item;
+ }
+
+ position = item = 0;
+ while (position < sizeof(stream_title) && item <= last_item) {
+ gint length = 0;
+
+ length = g_strlcpy(stream_title + position,
+ tag_items[item++],
+ sizeof(stream_title) - position);
+
+ position += length;
+
+ if (item <= last_item) {
+ length = g_strlcpy(stream_title + position,
+ " - ",
+ sizeof(stream_title) - position);
+
+ position += length;
+ }
+ }
+
+ icy_string = icy_server_metadata_string(stream_title, "");
+
+ if (icy_string == NULL)
+ return NULL;
+
+ Page *icy_metadata = Page::Copy(icy_string, (icy_string[0] * 16) + 1);
+
+ g_free(icy_string);
+
+ return icy_metadata;
+}
diff --git a/src/IcyMetaDataServer.hxx b/src/IcyMetaDataServer.hxx
new file mode 100644
index 000000000..ee685adcf
--- /dev/null
+++ b/src/IcyMetaDataServer.hxx
@@ -0,0 +1,36 @@
+/*
+ * 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_ICY_META_DATA_SERVER_HXX
+#define MPD_ICY_META_DATA_SERVER_HXX
+
+#include "TagType.h"
+
+struct Tag;
+class Page;
+
+char*
+icy_server_metadata_header(const char *name,
+ const char *genre, const char *url,
+ const char *content_type, int metaint);
+
+Page *
+icy_server_metadata_page(const Tag &tag, const enum tag_type *types);
+
+#endif
diff --git a/src/IdTable.hxx b/src/IdTable.hxx
new file mode 100644
index 000000000..8925fe8ab
--- /dev/null
+++ b/src/IdTable.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_ID_TABLE_HXX
+#define MPD_ID_TABLE_HXX
+
+#include "gcc.h"
+
+#include <algorithm>
+
+#include <assert.h>
+
+/**
+ * A table that maps id numbers to position numbers.
+ */
+class IdTable {
+ unsigned size;
+
+ unsigned next;
+
+ int *data;
+
+public:
+ IdTable(unsigned _size):size(_size), next(1), data(new int[size]) {
+ std::fill(data, data + size, -1);
+ }
+
+ ~IdTable() {
+ delete[] data;
+ }
+
+ int IdToPosition(unsigned id) const {
+ return id < size
+ ? data[id]
+ : -1;
+ }
+
+ unsigned GenerateId() {
+ assert(next > 0);
+ assert(next < size);
+
+ while (true) {
+ unsigned id = next;
+
+ ++next;
+ if (next == size)
+ next = 1;
+
+ if (data[id] < 0)
+ return id;
+ }
+ }
+
+ unsigned Insert(unsigned position) {
+ unsigned id = GenerateId();
+ data[id] = position;
+ return id;
+ }
+
+ void Move(unsigned id, unsigned position) {
+ assert(id < size);
+ assert(data[id] >= 0);
+
+ data[id] = position;
+ }
+
+ void Erase(unsigned id) {
+ assert(id < size);
+ assert(data[id] >= 0);
+
+ data[id] = -1;
+ }
+};
+
+#endif
diff --git a/src/Idle.cxx b/src/Idle.cxx
new file mode 100644
index 000000000..840a5d4e5
--- /dev/null
+++ b/src/Idle.cxx
@@ -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.
+ */
+
+/*
+ * Support library for the "idle" command.
+ *
+ */
+
+#include "config.h"
+#include "Idle.hxx"
+#include "GlobalEvents.hxx"
+
+#include <atomic>
+
+#include <assert.h>
+
+static std::atomic_uint idle_flags;
+
+static const char *const idle_names[] = {
+ "database",
+ "stored_playlist",
+ "playlist",
+ "player",
+ "mixer",
+ "output",
+ "options",
+ "sticker",
+ "update",
+ "subscription",
+ "message",
+ nullptr
+};
+
+void
+idle_add(unsigned flags)
+{
+ assert(flags != 0);
+
+ unsigned old_flags = idle_flags.fetch_or(flags);
+
+ if ((old_flags & flags) != flags)
+ GlobalEvents::Emit(GlobalEvents::IDLE);
+}
+
+unsigned
+idle_get(void)
+{
+ return idle_flags.exchange(0);
+}
+
+const char*const*
+idle_get_names(void)
+{
+ return idle_names;
+}
diff --git a/src/Idle.hxx b/src/Idle.hxx
new file mode 100644
index 000000000..e78d5a12a
--- /dev/null
+++ b/src/Idle.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.
+ */
+
+/*
+ * Support library for the "idle" command.
+ *
+ */
+
+#ifndef MPD_IDLE_HXX
+#define MPD_IDLE_HXX
+
+enum {
+ /** song database has been updated*/
+ IDLE_DATABASE = 0x1,
+
+ /** a stored playlist has been modified, created, deleted or
+ renamed */
+ IDLE_STORED_PLAYLIST = 0x2,
+
+ /** the current playlist has been modified */
+ IDLE_PLAYLIST = 0x4,
+
+ /** the player state has changed: play, stop, pause, seek, ... */
+ IDLE_PLAYER = 0x8,
+
+ /** the volume has been modified */
+ IDLE_MIXER = 0x10,
+
+ /** an audio output device has been enabled or disabled */
+ IDLE_OUTPUT = 0x20,
+
+ /** options have changed: crossfade, random, repeat, ... */
+ IDLE_OPTIONS = 0x40,
+
+ /** a sticker has been modified. */
+ IDLE_STICKER = 0x80,
+
+ /** a database update has started or finished. */
+ IDLE_UPDATE = 0x100,
+
+ /** a client has subscribed or unsubscribed to/from a channel */
+ IDLE_SUBSCRIPTION = 0x200,
+
+ /** a message on the subscribed channel was received */
+ IDLE_MESSAGE = 0x400,
+};
+
+/**
+ * Adds idle flag (with bitwise "or") and queues notifications to all
+ * clients.
+ */
+void
+idle_add(unsigned flags);
+
+/**
+ * Atomically reads and resets the global idle flags value.
+ */
+unsigned
+idle_get(void);
+
+/**
+ * Get idle names
+ */
+const char*const*
+idle_get_names(void);
+
+#endif
diff --git a/src/InotifyQueue.cxx b/src/InotifyQueue.cxx
new file mode 100644
index 000000000..419135dae
--- /dev/null
+++ b/src/InotifyQueue.cxx
@@ -0,0 +1,94 @@
+/*
+ * 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 "InotifyQueue.hxx"
+#include "UpdateGlue.hxx"
+#include "event/Loop.hxx"
+
+#include <glib.h>
+
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "inotify"
+
+enum {
+ /**
+ * Wait this long after the last change before calling
+ * update_enqueue(). This increases the probability that
+ * updates can be bundled.
+ */
+ INOTIFY_UPDATE_DELAY_S = 5,
+};
+
+void
+InotifyQueue::OnTimeout()
+{
+ unsigned id;
+
+ while (!queue.empty()) {
+ const char *uri_utf8 = queue.front().c_str();
+
+ id = update_enqueue(uri_utf8, false);
+ if (id == 0) {
+ /* retry later */
+ ScheduleSeconds(INOTIFY_UPDATE_DELAY_S);
+ return;
+ }
+
+ g_debug("updating '%s' job=%u", uri_utf8, id);
+
+ queue.pop_front();
+ }
+}
+
+static bool
+path_in(const char *path, const char *possible_parent)
+{
+ size_t length = strlen(possible_parent);
+
+ return path[0] == 0 ||
+ (memcmp(possible_parent, path, length) == 0 &&
+ (path[length] == 0 || path[length] == '/'));
+}
+
+void
+InotifyQueue::Enqueue(const char *uri_utf8)
+{
+ ScheduleSeconds(INOTIFY_UPDATE_DELAY_S);
+
+ for (auto i = queue.begin(), end = queue.end(); i != end;) {
+ const char *current_uri = i->c_str();
+
+ if (path_in(uri_utf8, current_uri))
+ /* already enqueued */
+ return;
+
+ if (path_in(current_uri, uri_utf8))
+ /* existing path is a sub-path of the new
+ path; we can dequeue the existing path and
+ update the new path instead */
+ i = queue.erase(i);
+ else
+ ++i;
+ }
+
+ queue.emplace_back(uri_utf8);
+}
diff --git a/src/InotifyQueue.hxx b/src/InotifyQueue.hxx
new file mode 100644
index 000000000..818621ef8
--- /dev/null
+++ b/src/InotifyQueue.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_INOTIFY_QUEUE_HXX
+#define MPD_INOTIFY_QUEUE_HXX
+
+#include "event/TimeoutMonitor.hxx"
+#include "gcc.h"
+
+#include <list>
+#include <string>
+
+class InotifyQueue final : private TimeoutMonitor {
+ std::list<std::string> queue;
+
+public:
+ InotifyQueue(EventLoop &_loop):TimeoutMonitor(_loop) {}
+
+ void Enqueue(const char *uri_utf8);
+
+private:
+ virtual void OnTimeout() override;
+};
+
+#endif
diff --git a/src/InotifySource.cxx b/src/InotifySource.cxx
new file mode 100644
index 000000000..5552ed578
--- /dev/null
+++ b/src/InotifySource.cxx
@@ -0,0 +1,141 @@
+/*
+ * 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 "InotifySource.hxx"
+#include "util/fifo_buffer.h"
+#include "fd_util.h"
+#include "mpd_error.h"
+
+#include <glib.h>
+
+#include <sys/inotify.h>
+#include <unistd.h>
+#include <errno.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "inotify"
+
+/**
+ * A GQuark for GError instances.
+ */
+static inline GQuark
+mpd_inotify_quark(void)
+{
+ return g_quark_from_static_string("inotify");
+}
+
+bool
+InotifySource::OnSocketReady(gcc_unused unsigned flags)
+{
+ void *dest;
+ size_t length;
+ ssize_t nbytes;
+
+ dest = fifo_buffer_write(buffer, &length);
+ if (dest == NULL)
+ MPD_ERROR("buffer full");
+
+ nbytes = read(Get(), dest, length);
+ if (nbytes < 0)
+ MPD_ERROR("failed to read from inotify: %s",
+ g_strerror(errno));
+ if (nbytes == 0)
+ MPD_ERROR("end of file from inotify");
+
+ fifo_buffer_append(buffer, nbytes);
+
+ while (true) {
+ const char *name;
+
+ const struct inotify_event *event =
+ (const struct inotify_event *)
+ fifo_buffer_read(buffer, &length);
+ if (event == NULL || length < sizeof(*event) ||
+ length < sizeof(*event) + event->len)
+ break;
+
+ if (event->len > 0 && event->name[event->len - 1] == 0)
+ name = event->name;
+ else
+ name = NULL;
+
+ callback(event->wd, event->mask, name, callback_ctx);
+ fifo_buffer_consume(buffer, sizeof(*event) + event->len);
+ }
+
+ return true;
+}
+
+inline
+InotifySource::InotifySource(EventLoop &_loop,
+ mpd_inotify_callback_t _callback, void *_ctx,
+ int _fd)
+ :SocketMonitor(_fd, _loop),
+ callback(_callback), callback_ctx(_ctx),
+ buffer(fifo_buffer_new(4096))
+{
+ ScheduleRead();
+
+}
+
+InotifySource *
+InotifySource::Create(EventLoop &loop,
+ mpd_inotify_callback_t callback, void *callback_ctx,
+ GError **error_r)
+{
+ int fd = inotify_init_cloexec();
+ if (fd < 0) {
+ g_set_error(error_r, mpd_inotify_quark(), errno,
+ "inotify_init() has failed: %s",
+ g_strerror(errno));
+ return NULL;
+ }
+
+ return new InotifySource(loop, callback, callback_ctx, fd);
+}
+
+InotifySource::~InotifySource()
+{
+ fifo_buffer_free(buffer);
+}
+
+int
+InotifySource::Add(const char *path_fs, unsigned mask, GError **error_r)
+{
+ int wd = inotify_add_watch(Get(), path_fs, mask);
+ if (wd < 0)
+ g_set_error(error_r, mpd_inotify_quark(), errno,
+ "inotify_add_watch() has failed: %s",
+ g_strerror(errno));
+
+ return wd;
+}
+
+void
+InotifySource::Remove(unsigned wd)
+{
+ int ret = inotify_rm_watch(Get(), wd);
+ if (ret < 0 && errno != EINVAL)
+ g_warning("inotify_rm_watch() has failed: %s",
+ g_strerror(errno));
+
+ /* EINVAL may happen here when the file has been deleted; the
+ kernel seems to auto-unregister deleted files */
+}
diff --git a/src/InotifySource.hxx b/src/InotifySource.hxx
new file mode 100644
index 000000000..e8f9ff03c
--- /dev/null
+++ b/src/InotifySource.hxx
@@ -0,0 +1,72 @@
+/*
+ * 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_INOTIFY_SOURCE_HXX
+#define MPD_INOTIFY_SOURCE_HXX
+
+#include "event/SocketMonitor.hxx"
+#include "gerror.h"
+#include "gcc.h"
+
+typedef void (*mpd_inotify_callback_t)(int wd, unsigned mask,
+ const char *name, void *ctx);
+
+class InotifySource final : private SocketMonitor {
+ mpd_inotify_callback_t callback;
+ void *callback_ctx;
+
+ struct fifo_buffer *buffer;
+
+ InotifySource(EventLoop &_loop,
+ mpd_inotify_callback_t callback, void *ctx, int fd);
+
+public:
+ /**
+ * Creates a new inotify source and registers it in the GLib main
+ * loop.
+ *
+ * @param a callback invoked for events received from the kernel
+ */
+ static InotifySource *Create(EventLoop &_loop,
+ mpd_inotify_callback_t callback,
+ void *ctx,
+ GError **error_r);
+
+ ~InotifySource();
+
+
+ /**
+ * Adds a path to the notify list.
+ *
+ * @return a watch descriptor or -1 on error
+ */
+ int Add(const char *path_fs, unsigned mask, GError **error_r);
+
+ /**
+ * Removes a path from the notify list.
+ *
+ * @param wd the watch descriptor returned by mpd_inotify_source_add()
+ */
+ void Remove(unsigned wd);
+
+private:
+ virtual bool OnSocketReady(unsigned flags) override;
+};
+
+#endif
diff --git a/src/InotifyUpdate.cxx b/src/InotifyUpdate.cxx
new file mode 100644
index 000000000..2de3bf627
--- /dev/null
+++ b/src/InotifyUpdate.cxx
@@ -0,0 +1,363 @@
+/*
+ * 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" /* must be first for large file support */
+#include "InotifyUpdate.hxx"
+#include "InotifySource.hxx"
+#include "InotifyQueue.hxx"
+#include "Mapper.hxx"
+#include "Main.hxx"
+#include "fs/Path.hxx"
+
+#include <glib.h>
+
+#include <map>
+#include <forward_list>
+
+#include <assert.h>
+#include <sys/inotify.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <dirent.h>
+#include <errno.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "inotify"
+
+enum {
+ IN_MASK = IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_DELETE_SELF
+ |IN_MOVE|IN_MOVE_SELF
+#ifdef IN_ONLYDIR
+ |IN_ONLYDIR
+#endif
+};
+
+struct WatchDirectory {
+ WatchDirectory *parent;
+
+ char *name;
+
+ int descriptor;
+
+ std::forward_list<WatchDirectory> children;
+
+ WatchDirectory(WatchDirectory *_parent, const char *_name,
+ int _descriptor)
+ :parent(_parent), name(g_strdup(_name)),
+ descriptor(_descriptor) {}
+
+ WatchDirectory(const WatchDirectory &) = delete;
+ WatchDirectory &operator=(const WatchDirectory &) = delete;
+
+ ~WatchDirectory() {
+ g_free(name);
+ }
+};
+
+static InotifySource *inotify_source;
+static InotifyQueue *inotify_queue;
+
+static unsigned inotify_max_depth;
+static WatchDirectory *inotify_root;
+static std::map<int, WatchDirectory *> inotify_directories;
+
+static void
+tree_add_watch_directory(WatchDirectory *directory)
+{
+ inotify_directories.insert(std::make_pair(directory->descriptor,
+ directory));
+}
+
+static void
+tree_remove_watch_directory(WatchDirectory *directory)
+{
+ auto i = inotify_directories.find(directory->descriptor);
+ assert(i != inotify_directories.end());
+ inotify_directories.erase(i);
+}
+
+static WatchDirectory *
+tree_find_watch_directory(int wd)
+{
+ auto i = inotify_directories.find(wd);
+ if (i == inotify_directories.end())
+ return nullptr;
+
+ return i->second;
+}
+
+static void
+disable_watch_directory(WatchDirectory &directory)
+{
+ tree_remove_watch_directory(&directory);
+
+ for (WatchDirectory &child : directory.children)
+ disable_watch_directory(child);
+
+ inotify_source->Remove(directory.descriptor);
+}
+
+static void
+remove_watch_directory(WatchDirectory *directory)
+{
+ assert(directory != NULL);
+
+ if (directory->parent == NULL) {
+ g_warning("music directory was removed - "
+ "cannot continue to watch it");
+ return;
+ }
+
+ disable_watch_directory(*directory);
+
+ /* remove it from the parent, which effectively deletes it */
+ directory->parent->children.remove_if([directory](const WatchDirectory &child){
+ return &child == directory;
+ });
+}
+
+static char *
+watch_directory_get_uri_fs(const WatchDirectory *directory)
+{
+ char *parent_uri, *uri;
+
+ if (directory->parent == NULL)
+ return NULL;
+
+ parent_uri = watch_directory_get_uri_fs(directory->parent);
+ if (parent_uri == NULL)
+ return g_strdup(directory->name);
+
+ uri = g_strconcat(parent_uri, "/", directory->name, NULL);
+ g_free(parent_uri);
+
+ return uri;
+}
+
+/* we don't look at "." / ".." nor files with newlines in their name */
+static bool skip_path(const char *path)
+{
+ return (path[0] == '.' && path[1] == 0) ||
+ (path[0] == '.' && path[1] == '.' && path[2] == 0) ||
+ strchr(path, '\n') != NULL;
+}
+
+static void
+recursive_watch_subdirectories(WatchDirectory *directory,
+ const char *path_fs, unsigned depth)
+{
+ GError *error = NULL;
+ DIR *dir;
+ struct dirent *ent;
+
+ assert(directory != NULL);
+ assert(depth <= inotify_max_depth);
+ assert(path_fs != NULL);
+
+ ++depth;
+
+ if (depth > inotify_max_depth)
+ return;
+
+ dir = opendir(path_fs);
+ if (dir == NULL) {
+ g_warning("Failed to open directory %s: %s",
+ path_fs, g_strerror(errno));
+ return;
+ }
+
+ while ((ent = readdir(dir))) {
+ char *child_path_fs;
+ struct stat st;
+ int ret;
+
+ if (skip_path(ent->d_name))
+ continue;
+
+ child_path_fs = g_strconcat(path_fs, "/", ent->d_name, NULL);
+ ret = stat(child_path_fs, &st);
+ if (ret < 0) {
+ g_warning("Failed to stat %s: %s",
+ child_path_fs, g_strerror(errno));
+ g_free(child_path_fs);
+ continue;
+ }
+
+ if (!S_ISDIR(st.st_mode)) {
+ g_free(child_path_fs);
+ continue;
+ }
+
+ ret = inotify_source->Add(child_path_fs, IN_MASK, &error);
+ if (ret < 0) {
+ g_warning("Failed to register %s: %s",
+ child_path_fs, error->message);
+ g_error_free(error);
+ error = NULL;
+ g_free(child_path_fs);
+ continue;
+ }
+
+ WatchDirectory *child = tree_find_watch_directory(ret);
+ if (child != NULL) {
+ /* already being watched */
+ g_free(child_path_fs);
+ continue;
+ }
+
+ directory->children.emplace_front(directory, ent->d_name, ret);
+ child = &directory->children.front();
+
+ tree_add_watch_directory(child);
+
+ recursive_watch_subdirectories(child, child_path_fs, depth);
+ g_free(child_path_fs);
+ }
+
+ closedir(dir);
+}
+
+G_GNUC_PURE
+static unsigned
+watch_directory_depth(const WatchDirectory *d)
+{
+ assert(d != NULL);
+
+ unsigned depth = 0;
+ while ((d = d->parent) != NULL)
+ ++depth;
+
+ return depth;
+}
+
+static void
+mpd_inotify_callback(int wd, unsigned mask,
+ G_GNUC_UNUSED const char *name, G_GNUC_UNUSED void *ctx)
+{
+ WatchDirectory *directory;
+ char *uri_fs;
+
+ /*g_debug("wd=%d mask=0x%x name='%s'", wd, mask, name);*/
+
+ directory = tree_find_watch_directory(wd);
+ if (directory == NULL)
+ return;
+
+ uri_fs = watch_directory_get_uri_fs(directory);
+
+ if ((mask & (IN_DELETE_SELF|IN_MOVE_SELF)) != 0) {
+ g_free(uri_fs);
+ remove_watch_directory(directory);
+ return;
+ }
+
+ if ((mask & (IN_ATTRIB|IN_CREATE|IN_MOVE)) != 0 &&
+ (mask & IN_ISDIR) != 0) {
+ /* a sub directory was changed: register those in
+ inotify */
+ const char *root = mapper_get_music_directory_fs().c_str();
+ const char *path_fs;
+ char *allocated = NULL;
+
+ if (uri_fs != NULL)
+ path_fs = allocated =
+ g_strconcat(root, "/", uri_fs, NULL);
+ else
+ path_fs = root;
+
+ recursive_watch_subdirectories(directory, path_fs,
+ watch_directory_depth(directory));
+ g_free(allocated);
+ }
+
+ if ((mask & (IN_CLOSE_WRITE|IN_MOVE|IN_DELETE)) != 0 ||
+ /* at the maximum depth, we watch out for newly created
+ directories */
+ (watch_directory_depth(directory) == inotify_max_depth &&
+ (mask & (IN_CREATE|IN_ISDIR)) == (IN_CREATE|IN_ISDIR))) {
+ /* a file was changed, or a directory was
+ moved/deleted: queue a database update */
+
+ if (uri_fs != nullptr) {
+ const std::string uri_utf8 = Path::ToUTF8(uri_fs);
+ if (!uri_utf8.empty())
+ inotify_queue->Enqueue(uri_utf8.c_str());
+ }
+ else
+ inotify_queue->Enqueue("");
+ }
+
+ g_free(uri_fs);
+}
+
+void
+mpd_inotify_init(unsigned max_depth)
+{
+ GError *error = NULL;
+
+ g_debug("initializing inotify");
+
+ const Path &path = mapper_get_music_directory_fs();
+ if (path.IsNull()) {
+ g_debug("no music directory configured");
+ return;
+ }
+
+ inotify_source = InotifySource::Create(*main_loop,
+ mpd_inotify_callback, nullptr,
+ &error);
+ if (inotify_source == NULL) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return;
+ }
+
+ inotify_max_depth = max_depth;
+
+ int descriptor = inotify_source->Add(path.c_str(), IN_MASK, &error);
+ if (descriptor < 0) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ delete inotify_source;
+ inotify_source = NULL;
+ return;
+ }
+
+ inotify_root = new WatchDirectory(nullptr, path.c_str(), descriptor);
+
+ tree_add_watch_directory(inotify_root);
+
+ recursive_watch_subdirectories(inotify_root, path.c_str(), 0);
+
+ inotify_queue = new InotifyQueue(*main_loop);
+
+ g_debug("watching music directory");
+}
+
+void
+mpd_inotify_finish(void)
+{
+ if (inotify_source == NULL)
+ return;
+
+ delete inotify_queue;
+ delete inotify_source;
+ delete inotify_root;
+ inotify_directories.clear();
+}
diff --git a/src/InotifyUpdate.hxx b/src/InotifyUpdate.hxx
new file mode 100644
index 000000000..ceb421553
--- /dev/null
+++ b/src/InotifyUpdate.hxx
@@ -0,0 +1,47 @@
+/*
+ * 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_INOTIFY_UPDATE_HXX
+#define MPD_INOTIFY_UPDATE_HXX
+
+#include "check.h"
+
+#ifdef HAVE_INOTIFY_INIT
+
+void
+mpd_inotify_init(unsigned max_depth);
+
+void
+mpd_inotify_finish(void);
+
+#else /* !HAVE_INOTIFY_INIT */
+
+static inline void
+mpd_inotify_init(G_GNUC_UNUSED unsigned max_depth)
+{
+}
+
+static inline void
+mpd_inotify_finish(void)
+{
+}
+
+#endif /* !HAVE_INOTIFY_INIT */
+
+#endif
diff --git a/src/InputInit.cxx b/src/InputInit.cxx
new file mode 100644
index 000000000..f6e40a6f9
--- /dev/null
+++ b/src/InputInit.cxx
@@ -0,0 +1,107 @@
+/*
+ * 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 "InputInit.hxx"
+#include "InputRegistry.hxx"
+#include "InputPlugin.hxx"
+#include "conf.h"
+
+#include <assert.h>
+#include <string.h>
+
+static inline GQuark
+input_quark(void)
+{
+ return g_quark_from_static_string("input");
+}
+
+/**
+ * Find the "input" configuration block for the specified plugin.
+ *
+ * @param plugin_name the name of the input plugin
+ * @return the configuration block, or NULL if none was configured
+ */
+static const struct config_param *
+input_plugin_config(const char *plugin_name, GError **error_r)
+{
+ const struct config_param *param = NULL;
+
+ while ((param = config_get_next_param(CONF_INPUT, param)) != NULL) {
+ const char *name = param->GetBlockValue("plugin");
+ if (name == NULL) {
+ g_set_error(error_r, input_quark(), 0,
+ "input configuration without 'plugin' name in line %d",
+ param->line);
+ return NULL;
+ }
+
+ if (strcmp(name, plugin_name) == 0)
+ return param;
+ }
+
+ return NULL;
+}
+
+bool
+input_stream_global_init(GError **error_r)
+{
+ const config_param empty;
+
+ GError *error = NULL;
+
+ for (unsigned i = 0; input_plugins[i] != NULL; ++i) {
+ const struct input_plugin *plugin = input_plugins[i];
+
+ assert(plugin->name != NULL);
+ assert(*plugin->name != 0);
+ assert(plugin->open != NULL);
+
+ const struct config_param *param =
+ input_plugin_config(plugin->name, &error);
+ if (param == nullptr) {
+ if (error != nullptr) {
+ g_propagate_error(error_r, error);
+ return false;
+ }
+
+ param = &empty;
+ } else if (!param->GetBlockValue("enabled", true))
+ /* the plugin is disabled in mpd.conf */
+ continue;
+
+ if (plugin->init == NULL || plugin->init(*param, &error))
+ input_plugins_enabled[i] = true;
+ else {
+ g_propagate_prefixed_error(error_r, error,
+ "Failed to initialize input plugin '%s': ",
+ plugin->name);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void input_stream_global_finish(void)
+{
+ input_plugins_for_each_enabled(plugin)
+ if (plugin->finish != NULL)
+ plugin->finish();
+}
diff --git a/src/InputInit.hxx b/src/InputInit.hxx
new file mode 100644
index 000000000..9d503e5a8
--- /dev/null
+++ b/src/InputInit.hxx
@@ -0,0 +1,39 @@
+/*
+ * 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_INIT_HXX
+#define MPD_INPUT_INIT_HXX
+
+#include "gerror.h"
+
+/**
+ * Initializes this library and all input_stream implementations.
+ *
+ * @param error_r location to store the error occurring, or NULL to
+ * ignore errors
+ */
+bool
+input_stream_global_init(GError **error_r);
+
+/**
+ * Deinitializes this library and all input_stream implementations.
+ */
+void input_stream_global_finish(void);
+
+#endif
diff --git a/src/InputInternal.cxx b/src/InputInternal.cxx
new file mode 100644
index 000000000..e110ebf0a
--- /dev/null
+++ b/src/InputInternal.cxx
@@ -0,0 +1,39 @@
+/*
+ * 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 "InputInternal.hxx"
+#include "InputStream.hxx"
+
+void
+input_stream_signal_client(struct input_stream *is)
+{
+ is->cond.broadcast();
+}
+
+void
+input_stream_set_ready(struct input_stream *is)
+{
+ const ScopeLock protect(is->mutex);
+
+ if (!is->ready) {
+ is->ready = true;
+ input_stream_signal_client(is);
+ }
+}
diff --git a/src/InputInternal.hxx b/src/InputInternal.hxx
new file mode 100644
index 000000000..019ac09c6
--- /dev/null
+++ b/src/InputInternal.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_INPUT_INTERNAL_HXX
+#define MPD_INPUT_INTERNAL_HXX
+
+#include "check.h"
+
+struct input_stream;
+
+void
+input_stream_signal_client(struct input_stream *is);
+
+void
+input_stream_set_ready(struct input_stream *is);
+
+#endif
diff --git a/src/InputPlugin.hxx b/src/InputPlugin.hxx
new file mode 100644
index 000000000..a1eb16339
--- /dev/null
+++ b/src/InputPlugin.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_INPUT_PLUGIN_HXX
+#define MPD_INPUT_PLUGIN_HXX
+
+#include "input_stream.h"
+
+#include <stddef.h>
+#include <sys/types.h>
+
+struct config_param;
+struct input_stream;
+
+struct input_plugin {
+ const char *name;
+
+ /**
+ * Global initialization. This method is called when MPD starts.
+ *
+ * @param error_r location to store the error occurring, or
+ * NULL to ignore errors
+ * @return true on success, false if the plugin should be
+ * disabled
+ */
+ bool (*init)(const config_param &param, GError **error_r);
+
+ /**
+ * Global deinitialization. Called once before MPD shuts
+ * down (only if init() has returned true).
+ */
+ void (*finish)(void);
+
+ struct input_stream *(*open)(const char *uri,
+ Mutex &mutex, Cond &cond,
+ GError **error_r);
+ void (*close)(struct input_stream *is);
+
+ /**
+ * Check for errors that may have occurred in the I/O thread.
+ * May be unimplemented for synchronous plugins.
+ *
+ * @return false on error
+ */
+ bool (*check)(struct input_stream *is, GError **error_r);
+
+ /**
+ * Update the public attributes. Call before access. Can be
+ * NULL if the plugin always keeps its attributes up to date.
+ */
+ void (*update)(struct input_stream *is);
+
+ Tag *(*tag)(struct input_stream *is);
+
+ /**
+ * Returns true if the next read operation will not block:
+ * either data is available, or end-of-stream has been
+ * reached, or an error has occurred.
+ *
+ * If this method is unimplemented, then it is assumed that
+ * reading will never block.
+ */
+ bool (*available)(struct input_stream *is);
+
+ size_t (*read)(struct input_stream *is, void *ptr, size_t size,
+ GError **error_r);
+ bool (*eof)(struct input_stream *is);
+ bool (*seek)(struct input_stream *is, goffset offset, int whence,
+ GError **error_r);
+};
+
+#endif
diff --git a/src/InputRegistry.cxx b/src/InputRegistry.cxx
new file mode 100644
index 000000000..75a106c24
--- /dev/null
+++ b/src/InputRegistry.cxx
@@ -0,0 +1,73 @@
+/*
+ * 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 "InputRegistry.hxx"
+#include "input/FileInputPlugin.hxx"
+
+#ifdef ENABLE_ARCHIVE
+#include "input/ArchiveInputPlugin.hxx"
+#endif
+
+#ifdef ENABLE_CURL
+#include "input/CurlInputPlugin.hxx"
+#endif
+
+#ifdef HAVE_FFMPEG
+#include "input/FfmpegInputPlugin.hxx"
+#endif
+
+#ifdef ENABLE_MMS
+#include "input/MmsInputPlugin.hxx"
+#endif
+
+#ifdef ENABLE_CDIO_PARANOIA
+#include "input/CdioParanoiaInputPlugin.hxx"
+#endif
+
+#ifdef ENABLE_DESPOTIFY
+#include "input/DespotifyInputPlugin.hxx"
+#endif
+
+#include <glib.h>
+
+const struct input_plugin *const input_plugins[] = {
+ &input_plugin_file,
+#ifdef ENABLE_ARCHIVE
+ &input_plugin_archive,
+#endif
+#ifdef ENABLE_CURL
+ &input_plugin_curl,
+#endif
+#ifdef HAVE_FFMPEG
+ &input_plugin_ffmpeg,
+#endif
+#ifdef ENABLE_MMS
+ &input_plugin_mms,
+#endif
+#ifdef ENABLE_CDIO_PARANOIA
+ &input_plugin_cdio_paranoia,
+#endif
+#ifdef ENABLE_DESPOTIFY
+ &input_plugin_despotify,
+#endif
+ NULL
+};
+
+bool input_plugins_enabled[G_N_ELEMENTS(input_plugins) - 1];
diff --git a/src/InputRegistry.hxx b/src/InputRegistry.hxx
new file mode 100644
index 000000000..a080d108b
--- /dev/null
+++ b/src/InputRegistry.hxx
@@ -0,0 +1,43 @@
+/*
+ * 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_REGISTRY_HXX
+#define MPD_INPUT_REGISTRY_HXX
+
+#include "check.h"
+
+/**
+ * NULL terminated list of all input plugins which were enabled at
+ * compile time.
+ */
+extern const struct input_plugin *const input_plugins[];
+
+extern bool input_plugins_enabled[];
+
+#define input_plugins_for_each(plugin) \
+ for (const struct input_plugin *plugin, \
+ *const*input_plugin_iterator = &input_plugins[0]; \
+ (plugin = *input_plugin_iterator) != NULL; \
+ ++input_plugin_iterator)
+
+#define input_plugins_for_each_enabled(plugin) \
+ input_plugins_for_each(plugin) \
+ if (input_plugins_enabled[input_plugin_iterator - input_plugins])
+
+#endif
diff --git a/src/InputStream.cxx b/src/InputStream.cxx
new file mode 100644
index 000000000..872d54fb7
--- /dev/null
+++ b/src/InputStream.cxx
@@ -0,0 +1,256 @@
+/*
+ * 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.
+ */
+
+#include "config.h"
+#include "InputStream.hxx"
+#include "InputRegistry.hxx"
+#include "InputPlugin.hxx"
+#include "input/RewindInputPlugin.hxx"
+#include "util/UriUtil.hxx"
+
+#include <glib.h>
+#include <assert.h>
+
+static inline GQuark
+input_quark(void)
+{
+ return g_quark_from_static_string("input");
+}
+
+struct input_stream *
+input_stream_open(const char *url,
+ Mutex &mutex, Cond &cond,
+ GError **error_r)
+{
+ GError *error = NULL;
+
+ assert(error_r == NULL || *error_r == NULL);
+
+ input_plugins_for_each_enabled(plugin) {
+ struct input_stream *is;
+
+ is = plugin->open(url, mutex, cond, &error);
+ if (is != NULL) {
+ assert(is->plugin.close != NULL);
+ assert(is->plugin.read != NULL);
+ assert(is->plugin.eof != NULL);
+ assert(!is->seekable || is->plugin.seek != NULL);
+
+ is = input_rewind_open(is);
+
+ return is;
+ } else if (error != NULL) {
+ g_propagate_error(error_r, error);
+ return NULL;
+ }
+ }
+
+ g_set_error(error_r, input_quark(), 0, "Unrecognized URI");
+ return NULL;
+}
+
+bool
+input_stream_check(struct input_stream *is, GError **error_r)
+{
+ assert(is != NULL);
+
+ return is->plugin.check == NULL ||
+ is->plugin.check(is, error_r);
+}
+
+void
+input_stream_update(struct input_stream *is)
+{
+ assert(is != NULL);
+
+ if (is->plugin.update != NULL)
+ is->plugin.update(is);
+}
+
+void
+input_stream_wait_ready(struct input_stream *is)
+{
+ assert(is != NULL);
+
+ while (true) {
+ input_stream_update(is);
+ if (is->ready)
+ break;
+
+ is->cond.wait(is->mutex);
+ }
+}
+
+void
+input_stream_lock_wait_ready(struct input_stream *is)
+{
+ assert(is != NULL);
+
+ const ScopeLock protect(is->mutex);
+ input_stream_wait_ready(is);
+}
+
+const char *
+input_stream_get_mime_type(const struct input_stream *is)
+{
+ assert(is != NULL);
+ assert(is->ready);
+
+ return is->mime.empty() ? nullptr : is->mime.c_str();
+}
+
+void
+input_stream_override_mime_type(struct input_stream *is, const char *mime)
+{
+ assert(is != NULL);
+ assert(is->ready);
+
+ is->mime = mime;
+}
+
+goffset
+input_stream_get_size(const struct input_stream *is)
+{
+ assert(is != NULL);
+ assert(is->ready);
+
+ return is->size;
+}
+
+goffset
+input_stream_get_offset(const struct input_stream *is)
+{
+ assert(is != NULL);
+ assert(is->ready);
+
+ return is->offset;
+}
+
+bool
+input_stream_is_seekable(const struct input_stream *is)
+{
+ assert(is != NULL);
+ assert(is->ready);
+
+ return is->seekable;
+}
+
+bool
+input_stream_cheap_seeking(const struct input_stream *is)
+{
+ return is->seekable && !uri_has_scheme(is->uri.c_str());
+}
+
+bool
+input_stream_seek(struct input_stream *is, goffset offset, int whence,
+ GError **error_r)
+{
+ assert(is != NULL);
+
+ if (is->plugin.seek == NULL)
+ return false;
+
+ return is->plugin.seek(is, offset, whence, error_r);
+}
+
+bool
+input_stream_lock_seek(struct input_stream *is, goffset offset, int whence,
+ GError **error_r)
+{
+ assert(is != NULL);
+
+ if (is->plugin.seek == NULL)
+ return false;
+
+ const ScopeLock protect(is->mutex);
+ return input_stream_seek(is, offset, whence, error_r);
+}
+
+Tag *
+input_stream_tag(struct input_stream *is)
+{
+ assert(is != NULL);
+
+ return is->plugin.tag != NULL
+ ? is->plugin.tag(is)
+ : NULL;
+}
+
+Tag *
+input_stream_lock_tag(struct input_stream *is)
+{
+ assert(is != NULL);
+
+ if (is->plugin.tag == NULL)
+ return nullptr;
+
+ const ScopeLock protect(is->mutex);
+ return input_stream_tag(is);
+}
+
+bool
+input_stream_available(struct input_stream *is)
+{
+ assert(is != NULL);
+
+ return is->plugin.available != NULL
+ ? is->plugin.available(is)
+ : true;
+}
+
+size_t
+input_stream_read(struct input_stream *is, void *ptr, size_t size,
+ GError **error_r)
+{
+ assert(ptr != NULL);
+ assert(size > 0);
+
+ return is->plugin.read(is, ptr, size, error_r);
+}
+
+size_t
+input_stream_lock_read(struct input_stream *is, void *ptr, size_t size,
+ GError **error_r)
+{
+ assert(ptr != NULL);
+ assert(size > 0);
+
+ const ScopeLock protect(is->mutex);
+ return input_stream_read(is, ptr, size, error_r);
+}
+
+void input_stream_close(struct input_stream *is)
+{
+ is->plugin.close(is);
+}
+
+bool input_stream_eof(struct input_stream *is)
+{
+ return is->plugin.eof(is);
+}
+
+bool
+input_stream_lock_eof(struct input_stream *is)
+{
+ assert(is != NULL);
+
+ const ScopeLock protect(is->mutex);
+ return input_stream_eof(is);
+}
+
diff --git a/src/InputStream.hxx b/src/InputStream.hxx
new file mode 100644
index 000000000..96172723a
--- /dev/null
+++ b/src/InputStream.hxx
@@ -0,0 +1,114 @@
+/*
+ * 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_STREAM_HXX
+#define MPD_INPUT_STREAM_HXX
+
+#include "input_stream.h"
+#include "check.h"
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+#include "gcc.h"
+
+#include <string>
+
+#include <assert.h>
+
+struct input_stream {
+ /**
+ * the plugin which implements this input stream
+ */
+ const struct input_plugin &plugin;
+
+ /**
+ * The absolute URI which was used to open this stream.
+ */
+ std::string uri;
+
+ /**
+ * A mutex that protects the mutable attributes of this object
+ * and its implementation. It must be locked before calling
+ * any of the public methods.
+ *
+ * This object is allocated by the client, and the client is
+ * responsible for freeing it.
+ */
+ Mutex &mutex;
+
+ /**
+ * A cond that gets signalled when the state of this object
+ * changes from the I/O thread. The client of this object may
+ * wait on it. Optional, may be NULL.
+ *
+ * This object is allocated by the client, and the client is
+ * responsible for freeing it.
+ */
+ Cond &cond;
+
+ /**
+ * indicates whether the stream is ready for reading and
+ * whether the other attributes in this struct are valid
+ */
+ bool ready;
+
+ /**
+ * if true, then the stream is fully seekable
+ */
+ bool seekable;
+
+ /**
+ * the size of the resource, or -1 if unknown
+ */
+ goffset size;
+
+ /**
+ * the current offset within the stream
+ */
+ goffset offset;
+
+ /**
+ * the MIME content type of the resource, or empty if unknown.
+ */
+ std::string mime;
+
+ input_stream(const input_plugin &_plugin,
+ const char *_uri, Mutex &_mutex, Cond &_cond)
+ :plugin(_plugin), uri(_uri),
+ mutex(_mutex), cond(_cond),
+ ready(false), seekable(false),
+ size(-1), offset(0) {
+ assert(_uri != NULL);
+ }
+};
+
+gcc_nonnull(1)
+static inline void
+input_stream_lock(struct input_stream *is)
+{
+ is->mutex.lock();
+}
+
+gcc_nonnull(1)
+static inline void
+input_stream_unlock(struct input_stream *is)
+{
+ is->mutex.unlock();
+}
+
+#endif
diff --git a/src/Instance.cxx b/src/Instance.cxx
new file mode 100644
index 000000000..9982e5826
--- /dev/null
+++ b/src/Instance.cxx
@@ -0,0 +1,48 @@
+/*
+ * 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 "Instance.hxx"
+#include "Partition.hxx"
+#include "Idle.hxx"
+
+void
+Instance::DeleteSong(const Song &song)
+{
+ partition->DeleteSong(song);
+}
+
+void
+Instance::DatabaseModified()
+{
+ partition->playlist.FullIncrementVersions();
+ idle_add(IDLE_DATABASE);
+}
+
+void
+Instance::TagModified()
+{
+ partition->playlist.TagChanged();
+}
+
+void
+Instance::SyncWithPlayer()
+{
+ partition->playlist.SyncWithPlayer(partition->pc);
+}
diff --git a/src/Instance.hxx b/src/Instance.hxx
new file mode 100644
index 000000000..896656b10
--- /dev/null
+++ b/src/Instance.hxx
@@ -0,0 +1,54 @@
+/*
+ * 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_INSTANCE_HXX
+#define MPD_INSTANCE_HXX
+
+#include "check.h"
+
+class ClientList;
+struct Partition;
+struct Song;
+
+struct Instance {
+ ClientList *client_list;
+
+ Partition *partition;
+
+ void DeleteSong(const Song &song);
+
+ /**
+ * The database has been modified. Propagate the change to
+ * all subsystems.
+ */
+ void DatabaseModified();
+
+ /**
+ * A tag in the play queue has been modified. Propagate the
+ * change to all subsystems.
+ */
+ void TagModified();
+
+ /**
+ * Synchronize the player with the play queue.
+ */
+ void SyncWithPlayer();
+};
+
+#endif
diff --git a/src/Listen.cxx b/src/Listen.cxx
new file mode 100644
index 000000000..659ed6bdf
--- /dev/null
+++ b/src/Listen.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 "Listen.hxx"
+#include "Main.hxx"
+#include "Instance.hxx"
+#include "Client.hxx"
+#include "conf.h"
+#include "event/ServerSocket.hxx"
+
+#include <string.h>
+#include <assert.h>
+
+#ifdef ENABLE_SYSTEMD_DAEMON
+#include <systemd/sd-daemon.h>
+#endif
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "listen"
+
+#define DEFAULT_PORT 6600
+
+class ClientListener final : public ServerSocket {
+public:
+ ClientListener():ServerSocket(*main_loop) {}
+
+private:
+ virtual void OnAccept(int fd, const sockaddr &address,
+ size_t address_length, int uid) {
+ client_new(*main_loop, *instance->partition,
+ fd, &address, address_length, uid);
+ }
+};
+
+static ClientListener *listen_socket;
+int listen_port;
+
+static bool
+listen_add_config_param(unsigned int port,
+ const struct config_param *param,
+ GError **error_r)
+{
+ assert(param != NULL);
+
+ if (0 == strcmp(param->value, "any")) {
+ return listen_socket->AddPort(port, error_r);
+ } else if (param->value[0] == '/') {
+ return listen_socket->AddPath(param->value, error_r);
+ } else {
+ return listen_socket->AddHost(param->value, port, error_r);
+ }
+}
+
+static bool
+listen_systemd_activation(GError **error_r)
+{
+#ifdef ENABLE_SYSTEMD_DAEMON
+ int n = sd_listen_fds(true);
+ if (n <= 0) {
+ if (n < 0)
+ g_warning("sd_listen_fds() failed: %s",
+ g_strerror(-n));
+ return false;
+ }
+
+ for (int i = SD_LISTEN_FDS_START, end = SD_LISTEN_FDS_START + n;
+ i != end; ++i)
+ if (!listen_socket->AddFD(i, error_r))
+ return false;
+
+ return true;
+#else
+ (void)error_r;
+ return false;
+#endif
+}
+
+bool
+listen_global_init(GError **error_r)
+{
+ assert(main_loop != nullptr);
+
+ int port = config_get_positive(CONF_PORT, DEFAULT_PORT);
+ const struct config_param *param =
+ config_get_next_param(CONF_BIND_TO_ADDRESS, NULL);
+ bool success;
+ GError *error = NULL;
+
+ listen_socket = new ClientListener();
+
+ if (listen_systemd_activation(&error))
+ return true;
+
+ if (error != NULL) {
+ g_propagate_error(error_r, error);
+ return false;
+ }
+
+ if (param != NULL) {
+ /* "bind_to_address" is configured, create listeners
+ for all values */
+
+ do {
+ success = listen_add_config_param(port, param, &error);
+ if (!success) {
+ delete listen_socket;
+ g_propagate_prefixed_error(error_r, error,
+ "Failed to listen on %s (line %i): ",
+ param->value, param->line);
+ return false;
+ }
+
+ param = config_get_next_param(CONF_BIND_TO_ADDRESS,
+ param);
+ } while (param != NULL);
+ } else {
+ /* no "bind_to_address" configured, bind the
+ configured port on all interfaces */
+
+ success = listen_socket->AddPort(port, error_r);
+ if (!success) {
+ delete listen_socket;
+ g_propagate_prefixed_error(error_r, error,
+ "Failed to listen on *:%d: ",
+ port);
+ return false;
+ }
+ }
+
+ if (!listen_socket->Open(error_r)) {
+ delete listen_socket;
+ return false;
+ }
+
+ listen_port = port;
+ return true;
+}
+
+void listen_global_finish(void)
+{
+ g_debug("listen_global_finish called");
+
+ assert(listen_socket != NULL);
+
+ delete listen_socket;
+}
diff --git a/src/Listen.hxx b/src/Listen.hxx
new file mode 100644
index 000000000..3e2be9e63
--- /dev/null
+++ b/src/Listen.hxx
@@ -0,0 +1,32 @@
+/*
+ * 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_LISTEN_HXX
+#define MPD_LISTEN_HXX
+
+#include "gerror.h"
+
+extern int listen_port;
+
+bool
+listen_global_init(GError **error_r);
+
+void listen_global_finish(void);
+
+#endif
diff --git a/src/Log.cxx b/src/Log.cxx
new file mode 100644
index 000000000..8a50e1efb
--- /dev/null
+++ b/src/Log.cxx
@@ -0,0 +1,339 @@
+/*
+ * 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 "Log.hxx"
+#include "conf.h"
+#include "fd_util.h"
+#include "mpd_error.h"
+
+#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
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "log"
+
+#define LOG_LEVEL_SECURE G_LOG_LEVEL_INFO
+
+#define LOG_DATE_BUF_SIZE 16
+#define LOG_DATE_LEN (LOG_DATE_BUF_SIZE - 1)
+
+static GLogLevelFlags log_threshold = G_LOG_LEVEL_MESSAGE;
+
+static const char *log_charset;
+
+static bool stdout_mode = true;
+static int out_fd;
+static char *out_filename;
+
+static void redirect_logs(int fd)
+{
+ assert(fd >= 0);
+ if (dup2(fd, STDOUT_FILENO) < 0)
+ MPD_ERROR("problems dup2 stdout : %s\n", strerror(errno));
+ if (dup2(fd, STDERR_FILENO) < 0)
+ MPD_ERROR("problems dup2 stderr : %s\n", strerror(errno));
+}
+
+static const char *log_date(void)
+{
+ static char buf[LOG_DATE_BUF_SIZE];
+ time_t t = time(NULL);
+ 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 && g_ascii_isspace(p[length - 1]))
+ --length;
+
+ return (int)length;
+}
+
+static void
+file_log_func(const gchar *log_domain,
+ GLogLevelFlags log_level,
+ const gchar *message, G_GNUC_UNUSED gpointer user_data)
+{
+ char *converted;
+
+ if (log_level > log_threshold)
+ return;
+
+ if (log_charset != NULL) {
+ converted = g_convert_with_fallback(message, -1,
+ log_charset, "utf-8",
+ NULL, NULL, NULL, NULL);
+ if (converted != NULL)
+ message = converted;
+ } else
+ converted = NULL;
+
+ if (log_domain == NULL)
+ log_domain = "";
+
+ fprintf(stderr, "%s%s%s%.*s\n",
+ stdout_mode ? "" : log_date(),
+ log_domain, *log_domain == 0 ? "" : ": ",
+ chomp_length(message), message);
+
+ g_free(converted);
+}
+
+static void
+log_init_stdout(void)
+{
+ g_log_set_default_handler(file_log_func, NULL);
+}
+
+static int
+open_log_file(void)
+{
+ assert(out_filename != NULL);
+
+ return open_cloexec(out_filename, O_CREAT | O_WRONLY | O_APPEND, 0666);
+}
+
+static bool
+log_init_file(unsigned line, GError **error_r)
+{
+ assert(out_filename != NULL);
+
+ out_fd = open_log_file();
+ if (out_fd < 0) {
+ g_set_error(error_r, log_quark(), errno,
+ "failed to open log file \"%s\" (config line %u): %s",
+ out_filename, line, g_strerror(errno));
+ return false;
+ }
+
+ g_log_set_default_handler(file_log_func, NULL);
+ 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 *log_domain,
+ GLogLevelFlags log_level, const gchar *message,
+ G_GNUC_UNUSED gpointer user_data)
+{
+ if (stdout_mode) {
+ /* fall back to the file log function during
+ startup */
+ file_log_func(log_domain, log_level,
+ message, user_data);
+ return;
+ }
+
+ if (log_level > log_threshold)
+ return;
+
+ if (log_domain == NULL)
+ log_domain = "";
+
+ syslog(glib_to_syslog_level(log_level), "%s%s%.*s",
+ log_domain, *log_domain == 0 ? "" : ": ",
+ chomp_length(message), message);
+}
+
+static void
+log_init_syslog(void)
+{
+ assert(out_filename == NULL);
+
+ openlog(PACKAGE, 0, LOG_DAEMON);
+ g_log_set_default_handler(syslog_log_func, NULL);
+}
+
+#endif
+
+static inline GLogLevelFlags
+parse_log_level(const char *value, unsigned line)
+{
+ if (0 == strcmp(value, "default"))
+ return G_LOG_LEVEL_MESSAGE;
+ if (0 == strcmp(value, "secure"))
+ return LOG_LEVEL_SECURE;
+ else if (0 == strcmp(value, "verbose"))
+ return G_LOG_LEVEL_DEBUG;
+ else {
+ MPD_ERROR("unknown log level \"%s\" at line %u\n",
+ value, line);
+ return G_LOG_LEVEL_MESSAGE;
+ }
+}
+
+void
+log_early_init(bool verbose)
+{
+ if (verbose)
+ log_threshold = G_LOG_LEVEL_DEBUG;
+
+ log_init_stdout();
+}
+
+bool
+log_init(bool verbose, bool use_stdout, GError **error_r)
+{
+ const struct config_param *param;
+
+ g_get_charset(&log_charset);
+
+ if (verbose)
+ log_threshold = G_LOG_LEVEL_DEBUG;
+ else if ((param = config_get_param(CONF_LOG_LEVEL)) != NULL)
+ log_threshold = parse_log_level(param->value, param->line);
+
+ if (use_stdout) {
+ log_init_stdout();
+ return true;
+ } else {
+ param = config_get_param(CONF_LOG_FILE);
+ if (param == NULL) {
+#ifdef HAVE_SYSLOG
+ /* no configuration: default to syslog (if
+ available) */
+ log_init_syslog();
+ return true;
+#else
+ g_set_error(error_r, log_quark(), 0,
+ "config parameter 'log_file' not found");
+ return false;
+#endif
+#ifdef HAVE_SYSLOG
+ } else if (strcmp(param->value, "syslog") == 0) {
+ log_init_syslog();
+ return true;
+#endif
+ } else {
+ out_filename = config_dup_path(CONF_LOG_FILE, error_r);
+ return out_filename != NULL &&
+ log_init_file(param->line, error_r);
+ }
+ }
+}
+
+static void
+close_log_files(void)
+{
+ if (stdout_mode)
+ return;
+
+#ifdef HAVE_SYSLOG
+ if (out_filename == NULL)
+ closelog();
+#endif
+}
+
+void
+log_deinit(void)
+{
+ close_log_files();
+ g_free(out_filename);
+}
+
+
+void setup_log_output(bool use_stdout)
+{
+ fflush(NULL);
+ if (!use_stdout) {
+#ifndef WIN32
+ if (out_filename == NULL)
+ out_fd = open("/dev/null", O_WRONLY);
+#endif
+
+ if (out_fd >= 0) {
+ redirect_logs(out_fd);
+ close(out_fd);
+ }
+
+ stdout_mode = false;
+ log_charset = NULL;
+ }
+}
+
+int cycle_log_files(void)
+{
+ int fd;
+
+ if (stdout_mode || out_filename == NULL)
+ return 0;
+ assert(out_filename);
+
+ g_debug("Cycling log files...\n");
+ close_log_files();
+
+ fd = open_log_file();
+ if (fd < 0) {
+ g_warning("error re-opening log file: %s\n", out_filename);
+ return -1;
+ }
+
+ redirect_logs(fd);
+ g_debug("Done cycling log files\n");
+ return 0;
+}
diff --git a/src/Log.hxx b/src/Log.hxx
new file mode 100644
index 000000000..1010721b5
--- /dev/null
+++ b/src/Log.hxx
@@ -0,0 +1,53 @@
+/*
+ * 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_HXX
+#define MPD_LOG_HXX
+
+#include <glib.h>
+
+G_GNUC_CONST
+static inline GQuark
+log_quark(void)
+{
+ return g_quark_from_static_string("log");
+}
+
+/**
+ * Configure a logging destination for daemon startup, before the
+ * configuration file is read. This allows the daemon to use the
+ * logging library (and the command line verbose level) before it's
+ * daemonized.
+ *
+ * @param verbose true when the program is started with --verbose
+ */
+void
+log_early_init(bool verbose);
+
+bool
+log_init(bool verbose, bool use_stdout, GError **error_r);
+
+void
+log_deinit(void);
+
+void setup_log_output(bool use_stdout);
+
+int cycle_log_files(void);
+
+#endif /* LOG_H */
diff --git a/src/Main.cxx b/src/Main.cxx
new file mode 100644
index 000000000..c0824fc55
--- /dev/null
+++ b/src/Main.cxx
@@ -0,0 +1,597 @@
+/*
+ * 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 "Main.hxx"
+#include "Instance.hxx"
+#include "CommandLine.hxx"
+#include "PlaylistFile.hxx"
+#include "PlaylistGlobal.hxx"
+#include "UpdateGlue.hxx"
+#include "MusicChunk.hxx"
+#include "StateFile.hxx"
+#include "PlayerThread.hxx"
+#include "Mapper.hxx"
+#include "DatabaseGlue.hxx"
+#include "DatabaseSimple.hxx"
+#include "Permission.hxx"
+#include "Listen.hxx"
+#include "Client.hxx"
+#include "ClientList.hxx"
+#include "AllCommands.hxx"
+#include "Partition.hxx"
+#include "Volume.hxx"
+#include "OutputAll.hxx"
+#include "Tag.hxx"
+#include "conf.h"
+#include "replay_gain_config.h"
+#include "Idle.hxx"
+#include "SignalHandlers.hxx"
+#include "Log.hxx"
+#include "GlobalEvents.hxx"
+#include "InputInit.hxx"
+#include "event/Loop.hxx"
+#include "IOThread.hxx"
+#include "fs/Path.hxx"
+#include "PlaylistRegistry.hxx"
+#include "ZeroconfGlue.hxx"
+#include "DecoderList.hxx"
+#include "AudioConfig.hxx"
+#include "pcm/PcmResample.hxx"
+
+extern "C" {
+#include "daemon.h"
+#include "stats.h"
+}
+
+#include "mpd_error.h"
+
+#ifdef ENABLE_INOTIFY
+#include "InotifyUpdate.hxx"
+#endif
+
+#ifdef ENABLE_SQLITE
+#include "StickerDatabase.hxx"
+#endif
+
+#ifdef ENABLE_ARCHIVE
+#include "ArchiveList.hxx"
+#endif
+
+#include <glib.h>
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+
+#ifdef HAVE_LOCALE_H
+#include <locale.h>
+#endif
+
+#ifdef WIN32
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#endif
+
+enum {
+ DEFAULT_BUFFER_SIZE = 2048,
+ DEFAULT_BUFFER_BEFORE_PLAY = 10,
+};
+
+GThread *main_task;
+EventLoop *main_loop;
+
+Instance *instance;
+
+static StateFile *state_file;
+
+static inline GQuark main_quark()
+{
+ return g_quark_from_static_string ("main");
+}
+
+static bool
+glue_daemonize_init(const struct options *options, GError **error_r)
+{
+ GError *error = NULL;
+
+ char *pid_file = config_dup_path(CONF_PID_FILE, &error);
+ if (pid_file == NULL && error != NULL) {
+ g_propagate_error(error_r, error);
+ return false;
+ }
+
+ daemonize_init(config_get_string(CONF_USER, NULL),
+ config_get_string(CONF_GROUP, NULL),
+ pid_file);
+ g_free(pid_file);
+
+ if (options->kill)
+ daemonize_kill();
+
+ return true;
+}
+
+static bool
+glue_mapper_init(GError **error_r)
+{
+ GError *error = NULL;
+ char *music_dir = config_dup_path(CONF_MUSIC_DIR, &error);
+ if (music_dir == NULL && error != NULL) {
+ g_propagate_error(error_r, error);
+ return false;
+ }
+
+ char *playlist_dir = config_dup_path(CONF_PLAYLIST_DIR, &error);
+ if (playlist_dir == NULL && error != NULL) {
+ g_propagate_error(error_r, error);
+ return false;
+ }
+
+ if (music_dir == NULL)
+ music_dir = g_strdup(g_get_user_special_dir(G_USER_DIRECTORY_MUSIC));
+
+ if (!mapper_init(music_dir, playlist_dir, &error)) {
+ g_propagate_error(error_r, error);
+ return false;
+ }
+
+ g_free(music_dir);
+ g_free(playlist_dir);
+ return true;
+}
+
+/**
+ * Returns the database. If this function returns false, this has not
+ * succeeded, and the caller should create the database after the
+ * process has been daemonized.
+ */
+static bool
+glue_db_init_and_load(void)
+{
+ const struct config_param *param = config_get_param(CONF_DATABASE);
+ const struct config_param *path = config_get_param(CONF_DB_FILE);
+
+ if (param != NULL && path != NULL)
+ g_message("Found both 'database' and 'db_file' setting - ignoring the latter");
+
+ GError *error = NULL;
+ bool ret;
+
+ if (!mapper_has_music_directory()) {
+ if (param != NULL)
+ g_message("Found database setting without "
+ "music_directory - disabling database");
+ if (path != NULL)
+ g_message("Found db_file setting without "
+ "music_directory - disabling database");
+ return true;
+ }
+
+ struct config_param *allocated = NULL;
+
+ if (param == NULL && path != NULL) {
+ allocated = new config_param("database", path->line);
+ allocated->AddBlockParam("path", path->value, path->line);
+ param = allocated;
+ }
+
+ if (!DatabaseGlobalInit(*param, &error))
+ MPD_ERROR("%s", error->message);
+
+ delete allocated;
+
+ ret = DatabaseGlobalOpen(&error);
+ if (!ret)
+ MPD_ERROR("%s", error->message);
+
+ /* run database update after daemonization? */
+ return !db_is_simple() || db_exists();
+}
+
+/**
+ * Configure and initialize the sticker subsystem.
+ */
+static void
+glue_sticker_init(void)
+{
+#ifdef ENABLE_SQLITE
+ GError *error = NULL;
+ char *sticker_file = config_dup_path(CONF_STICKER_FILE, &error);
+ if (sticker_file == NULL && error != NULL)
+ MPD_ERROR("%s", error->message);
+
+ if (!sticker_global_init(sticker_file, &error))
+ MPD_ERROR("%s", error->message);
+
+ g_free(sticker_file);
+#endif
+}
+
+static bool
+glue_state_file_init(GError **error_r)
+{
+ GError *error = NULL;
+
+ char *path = config_dup_path(CONF_STATE_FILE, &error);
+ if (path == nullptr) {
+ if (error != nullptr) {
+ g_propagate_error(error_r, error);
+ return false;
+ }
+
+ return true;
+ }
+
+ Path path_fs = Path::FromUTF8(path);
+
+ if (path_fs.IsNull()) {
+ g_free(path);
+ g_set_error(error_r, main_quark(), 0,
+ "Failed to convert state file path to FS encoding");
+ return false;
+ }
+
+ state_file = new StateFile(std::move(path_fs), path,
+ *instance->partition, *main_loop);
+ g_free(path);
+
+ state_file->Read();
+ return true;
+}
+
+/**
+ * Windows-only initialization of the Winsock2 library.
+ */
+static void winsock_init(void)
+{
+#ifdef WIN32
+ WSADATA sockinfo;
+ int retval;
+
+ retval = WSAStartup(MAKEWORD(2, 2), &sockinfo);
+ if(retval != 0)
+ {
+ MPD_ERROR("Attempt to open Winsock2 failed; error code %d\n",
+ retval);
+ }
+
+ if (LOBYTE(sockinfo.wVersion) != 2)
+ {
+ MPD_ERROR("We use Winsock2 but your version is either too new "
+ "or old; please install Winsock 2.x\n");
+ }
+#endif
+}
+
+/**
+ * Initialize the decoder and player core, including the music pipe.
+ */
+static void
+initialize_decoder_and_player(void)
+{
+ const struct config_param *param;
+ char *test;
+ size_t buffer_size;
+ float perc;
+ unsigned buffered_chunks;
+ unsigned buffered_before_play;
+
+ param = config_get_param(CONF_AUDIO_BUFFER_SIZE);
+ if (param != NULL) {
+ long tmp = strtol(param->value, &test, 10);
+ if (*test != '\0' || tmp <= 0 || tmp == LONG_MAX)
+ MPD_ERROR("buffer size \"%s\" is not a positive integer, "
+ "line %i\n", param->value, param->line);
+ buffer_size = tmp;
+ } else
+ buffer_size = DEFAULT_BUFFER_SIZE;
+
+ buffer_size *= 1024;
+
+ buffered_chunks = buffer_size / CHUNK_SIZE;
+
+ if (buffered_chunks >= 1 << 15)
+ MPD_ERROR("buffer size \"%li\" is too big\n", (long)buffer_size);
+
+ param = config_get_param(CONF_BUFFER_BEFORE_PLAY);
+ if (param != NULL) {
+ perc = strtod(param->value, &test);
+ if (*test != '%' || perc < 0 || perc > 100) {
+ MPD_ERROR("buffered before play \"%s\" is not a positive "
+ "percentage and less than 100 percent, line %i",
+ param->value, param->line);
+ }
+ } else
+ perc = DEFAULT_BUFFER_BEFORE_PLAY;
+
+ buffered_before_play = (perc / 100) * buffered_chunks;
+ if (buffered_before_play > buffered_chunks)
+ buffered_before_play = buffered_chunks;
+
+ const unsigned max_length =
+ config_get_positive(CONF_MAX_PLAYLIST_LENGTH,
+ DEFAULT_PLAYLIST_MAX_LENGTH);
+
+ instance->partition = new Partition(*instance,
+ max_length,
+ buffered_chunks,
+ buffered_before_play);
+}
+
+/**
+ * Handler for GlobalEvents::IDLE.
+ */
+static void
+idle_event_emitted(void)
+{
+ /* send "idle" notifications to all subscribed
+ clients */
+ unsigned flags = idle_get();
+ if (flags != 0)
+ instance->client_list->IdleAdd(flags);
+
+ if (flags & (IDLE_PLAYLIST|IDLE_PLAYER|IDLE_MIXER|IDLE_OUTPUT) &&
+ state_file != nullptr)
+ state_file->CheckModified();
+}
+
+/**
+ * Handler for GlobalEvents::SHUTDOWN.
+ */
+static void
+shutdown_event_emitted(void)
+{
+ main_loop->Break();
+}
+
+int main(int argc, char *argv[])
+{
+#ifdef WIN32
+ return win32_main(argc, argv);
+#else
+ return mpd_main(argc, argv);
+#endif
+}
+
+int mpd_main(int argc, char *argv[])
+{
+ struct options options;
+ clock_t start;
+ bool create_db;
+ GError *error = NULL;
+ bool success;
+
+ daemonize_close_stdin();
+
+#ifdef HAVE_LOCALE_H
+ /* initialize locale */
+ setlocale(LC_CTYPE,"");
+#endif
+
+ g_set_application_name("Music Player Daemon");
+
+#if !GLIB_CHECK_VERSION(2,32,0)
+ /* enable GLib's thread safety code */
+ g_thread_init(NULL);
+#endif
+
+ io_thread_init();
+ winsock_init();
+ config_global_init();
+
+ success = parse_cmdline(argc, argv, &options, &error);
+ if (!success) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
+ if (!glue_daemonize_init(&options, &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
+ stats_global_init();
+ tag_lib_init();
+
+ if (!log_init(options.verbose, options.log_stderr, &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
+ main_task = g_thread_self();
+ main_loop = new EventLoop(EventLoop::Default());
+
+ instance = new Instance();
+
+ const unsigned max_clients = config_get_positive(CONF_MAX_CONN, 10);
+ instance->client_list = new ClientList(max_clients);
+
+ success = listen_global_init(&error);
+ if (!success) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
+ daemonize_set_user();
+
+ GlobalEvents::Initialize();
+ GlobalEvents::Register(GlobalEvents::IDLE, idle_event_emitted);
+ GlobalEvents::Register(GlobalEvents::SHUTDOWN, shutdown_event_emitted);
+
+ Path::GlobalInit();
+
+ if (!glue_mapper_init(&error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
+ initPermissions();
+ playlist_global_init();
+ spl_global_init();
+#ifdef ENABLE_ARCHIVE
+ archive_plugin_init_all();
+#endif
+
+ if (!pcm_resample_global_init(&error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
+ decoder_plugin_init_all();
+ update_global_init();
+
+ create_db = !glue_db_init_and_load();
+
+ glue_sticker_init();
+
+ command_init();
+ initialize_decoder_and_player();
+ volume_init();
+ initAudioConfig();
+ audio_output_all_init(&instance->partition->pc);
+ client_manager_init();
+ replay_gain_global_init();
+
+ if (!input_stream_global_init(&error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
+ playlist_list_global_init();
+
+ daemonize(options.daemon);
+
+ setup_log_output(options.log_stderr);
+
+ initSigHandlers();
+
+ if (!io_thread_start(&error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
+ ZeroconfInit(*main_loop);
+
+ player_create(&instance->partition->pc);
+
+ if (create_db) {
+ /* the database failed to load: recreate the
+ database */
+ unsigned job = update_enqueue(NULL, true);
+ if (job == 0)
+ MPD_ERROR("directory update failed");
+ }
+
+ if (!glue_state_file_init(&error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
+ audio_output_all_set_replay_gain_mode(replay_gain_get_real_mode(instance->partition->playlist.queue.random));
+
+ success = config_get_bool(CONF_AUTO_UPDATE, false);
+#ifdef ENABLE_INOTIFY
+ if (success && mapper_has_music_directory())
+ mpd_inotify_init(config_get_unsigned(CONF_AUTO_UPDATE_DEPTH,
+ G_MAXUINT));
+#else
+ if (success)
+ g_warning("inotify: auto_update was disabled. enable during compilation phase");
+#endif
+
+ config_global_check();
+
+ /* enable all audio outputs (if not already done by
+ playlist_state_restore() */
+ instance->partition->pc.UpdateAudio();
+
+#ifdef WIN32
+ win32_app_started();
+#endif
+
+ /* run the main loop */
+ main_loop->Run();
+
+#ifdef WIN32
+ win32_app_stopping();
+#endif
+
+ /* cleanup */
+
+#ifdef ENABLE_INOTIFY
+ mpd_inotify_finish();
+#endif
+
+ if (state_file != nullptr) {
+ state_file->Write();
+ delete state_file;
+ }
+
+ instance->partition->pc.Kill();
+ ZeroconfDeinit();
+ listen_global_finish();
+ delete instance->client_list;
+
+ start = clock();
+ DatabaseGlobalDeinit();
+ g_debug("db_finish took %f seconds",
+ ((float)(clock()-start))/CLOCKS_PER_SEC);
+
+#ifdef ENABLE_SQLITE
+ sticker_global_finish();
+#endif
+
+ GlobalEvents::Deinitialize();
+
+ playlist_list_global_finish();
+ input_stream_global_finish();
+ audio_output_all_finish();
+ volume_finish();
+ mapper_finish();
+ delete instance->partition;
+ command_finish();
+ update_global_finish();
+ decoder_plugin_deinit_all();
+#ifdef ENABLE_ARCHIVE
+ archive_plugin_deinit_all();
+#endif
+ config_global_finish();
+ stats_global_finish();
+ io_thread_deinit();
+ delete instance;
+ delete main_loop;
+ daemonize_finish();
+#ifdef WIN32
+ WSACleanup();
+#endif
+
+ log_deinit();
+ return EXIT_SUCCESS;
+}
diff --git a/src/Main.hxx b/src/Main.hxx
new file mode 100644
index 000000000..e403d5669
--- /dev/null
+++ b/src/Main.hxx
@@ -0,0 +1,75 @@
+/*
+ * 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.
+ */
+
+#ifndef MPD_MAIN_HXX
+#define MPD_MAIN_HXX
+
+#include <glib.h>
+
+class EventLoop;
+struct Instance;
+
+extern GThread *main_task;
+
+extern EventLoop *main_loop;
+
+extern Instance *instance;
+
+/**
+ * A entry point for application.
+ * On non-Windows platforms this is called directly from main()
+ * On Windows platform this is called from win32_main()
+ * after doing some initialization.
+ */
+int mpd_main(int argc, char *argv[]);
+
+#ifdef WIN32
+
+/**
+ * If program is run as windows service performs nessesary initialization
+ * and then calls mpd_main() with specified arguments.
+ * If program is run as a regular application calls mpd_main() immediately.
+ */
+int
+win32_main(int argc, char *argv[]);
+
+/**
+ * When running as a service reports to service control manager
+ * that our service is started.
+ * When running as a console application enables console handler that will
+ * trigger GlobalEvents::SHUTDOWN when user closes console window
+ * or presses Ctrl+C.
+ * This function should be called just before entering main loop.
+ */
+void
+win32_app_started(void);
+
+/**
+ * When running as a service reports to service control manager
+ * that our service is about to stop.
+ * When running as a console application enables console handler that will
+ * catch all shutdown requests and ignore them.
+ * This function should be called just after leaving main loop.
+ */
+void
+win32_app_stopping(void);
+
+#endif
+
+#endif
diff --git a/src/Mapper.cxx b/src/Mapper.cxx
new file mode 100644
index 000000000..6f4a9cdcc
--- /dev/null
+++ b/src/Mapper.cxx
@@ -0,0 +1,302 @@
+/*
+ * 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.
+ */
+
+/*
+ * Maps directory and song objects to file system paths.
+ */
+
+#include "config.h"
+#include "Mapper.hxx"
+#include "Directory.hxx"
+#include "Song.hxx"
+#include "fs/Path.hxx"
+#include "fs/FileSystem.hxx"
+#include "fs/DirectoryReader.hxx"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <dirent.h>
+
+/**
+ * The absolute path of the music directory encoded in UTF-8.
+ */
+static char *music_dir_utf8;
+static size_t music_dir_utf8_length;
+
+/**
+ * The absolute path of the music directory encoded in the filesystem
+ * character set.
+ */
+static Path music_dir_fs = Path::Null();
+static size_t music_dir_fs_length;
+
+/**
+ * The absolute path of the playlist directory encoded in the
+ * filesystem character set.
+ */
+static Path playlist_dir_fs = Path::Null();
+
+static inline GQuark
+mapper_quark()
+{
+ return g_quark_from_static_string ("mapper");
+}
+
+/**
+ * Duplicate a string, chop all trailing slashes.
+ */
+static char *
+strdup_chop_slash(const char *path_fs)
+{
+ size_t length = strlen(path_fs);
+
+ while (length > 0 && path_fs[length - 1] == G_DIR_SEPARATOR)
+ --length;
+
+ return g_strndup(path_fs, length);
+}
+
+static void
+check_directory(const char *path_utf8, const Path &path_fs)
+{
+ struct stat st;
+ if (!StatFile(path_fs, st)) {
+ g_warning("Failed to stat directory \"%s\": %s",
+ path_utf8, g_strerror(errno));
+ return;
+ }
+
+ if (!S_ISDIR(st.st_mode)) {
+ g_warning("Not a directory: %s", path_utf8);
+ return;
+ }
+
+#ifndef WIN32
+ const Path x = Path::Build(path_fs, ".");
+ if (!StatFile(x, st) && errno == EACCES)
+ g_warning("No permission to traverse (\"execute\") directory: %s",
+ path_utf8);
+#endif
+
+ const DirectoryReader reader(path_fs);
+ if (reader.HasFailed() && errno == EACCES)
+ g_warning("No permission to read directory: %s", path_utf8);
+}
+
+static bool
+mapper_set_music_dir(const char *path_utf8, GError **error_r)
+{
+ music_dir_fs = Path::FromUTF8(path_utf8);
+ if (music_dir_fs.IsNull()) {
+ g_set_error(error_r, mapper_quark(), 0,
+ "Failed to convert music path to FS encoding");
+ return false;
+ }
+
+ music_dir_fs_length = music_dir_fs.length();
+
+ music_dir_utf8 = strdup_chop_slash(path_utf8);
+ music_dir_utf8_length = strlen(music_dir_utf8);
+
+ check_directory(path_utf8, music_dir_fs);
+
+ return true;
+}
+
+static bool
+mapper_set_playlist_dir(const char *path_utf8, GError **error_r)
+{
+ playlist_dir_fs = Path::FromUTF8(path_utf8);
+ if (playlist_dir_fs.IsNull()) {
+ g_set_error(error_r, mapper_quark(), 0,
+ "Failed to convert playlist path to FS encoding");
+ return false;
+ }
+
+ check_directory(path_utf8, playlist_dir_fs);
+ return true;
+}
+
+bool mapper_init(const char *_music_dir, const char *_playlist_dir,
+ GError **error_r)
+{
+ if (_music_dir != NULL)
+ if (!mapper_set_music_dir(_music_dir, error_r))
+ return false;
+
+ if (_playlist_dir != NULL)
+ if (!mapper_set_playlist_dir(_playlist_dir, error_r))
+ return false;
+
+ return true;
+}
+
+void mapper_finish(void)
+{
+ g_free(music_dir_utf8);
+}
+
+const char *
+mapper_get_music_directory_utf8(void)
+{
+ return music_dir_utf8;
+}
+
+const Path &
+mapper_get_music_directory_fs(void)
+{
+ return music_dir_fs;
+}
+
+const char *
+map_to_relative_path(const char *path_utf8)
+{
+ return music_dir_utf8 != NULL &&
+ memcmp(path_utf8, music_dir_utf8,
+ music_dir_utf8_length) == 0 &&
+ G_IS_DIR_SEPARATOR(path_utf8[music_dir_utf8_length])
+ ? path_utf8 + music_dir_utf8_length + 1
+ : path_utf8;
+}
+
+Path
+map_uri_fs(const char *uri)
+{
+ assert(uri != NULL);
+ assert(*uri != '/');
+
+ if (music_dir_fs.IsNull())
+ return Path::Null();
+
+ const Path uri_fs = Path::FromUTF8(uri);
+ if (uri_fs.IsNull())
+ return Path::Null();
+
+ return Path::Build(music_dir_fs, uri_fs);
+}
+
+Path
+map_directory_fs(const Directory *directory)
+{
+ assert(music_dir_utf8 != NULL);
+ assert(!music_dir_fs.IsNull());
+
+ if (directory->IsRoot())
+ return music_dir_fs;
+
+ return map_uri_fs(directory->GetPath());
+}
+
+Path
+map_directory_child_fs(const Directory *directory, const char *name)
+{
+ assert(music_dir_utf8 != NULL);
+ assert(!music_dir_fs.IsNull());
+
+ /* check for invalid or unauthorized base names */
+ if (*name == 0 || strchr(name, '/') != NULL ||
+ strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
+ return Path::Null();
+
+ const Path parent_fs = map_directory_fs(directory);
+ if (parent_fs.IsNull())
+ return Path::Null();
+
+ const Path name_fs = Path::FromUTF8(name);
+ if (name_fs.IsNull())
+ return Path::Null();
+
+ return Path::Build(parent_fs, name_fs);
+}
+
+/**
+ * Map a song object that was created by song_dup_detached(). It does
+ * not have a real parent directory, only the dummy object
+ * #detached_root.
+ */
+static Path
+map_detached_song_fs(const char *uri_utf8)
+{
+ Path uri_fs = Path::FromUTF8(uri_utf8);
+ if (uri_fs.IsNull())
+ return Path::Null();
+
+ return Path::Build(music_dir_fs, uri_fs);
+}
+
+Path
+map_song_fs(const Song *song)
+{
+ assert(song->IsFile());
+
+ if (song->IsInDatabase())
+ return song->IsDetached()
+ ? map_detached_song_fs(song->uri)
+ : map_directory_child_fs(song->parent, song->uri);
+ else
+ return Path::FromUTF8(song->uri);
+}
+
+char *
+map_fs_to_utf8(const char *path_fs)
+{
+ if (!music_dir_fs.IsNull() &&
+ strncmp(path_fs, music_dir_fs.c_str(), music_dir_fs_length) == 0 &&
+ G_IS_DIR_SEPARATOR(path_fs[music_dir_fs_length]))
+ /* remove musicDir prefix */
+ path_fs += music_dir_fs_length + 1;
+ else if (G_IS_DIR_SEPARATOR(path_fs[0]))
+ /* not within musicDir */
+ return NULL;
+
+ while (path_fs[0] == G_DIR_SEPARATOR)
+ ++path_fs;
+
+ const std::string path_utf8 = Path::ToUTF8(path_fs);
+ if (path_utf8.empty())
+ return nullptr;
+
+ return g_strdup(path_utf8.c_str());
+}
+
+const Path &
+map_spl_path(void)
+{
+ return playlist_dir_fs;
+}
+
+Path
+map_spl_utf8_to_fs(const char *name)
+{
+ if (playlist_dir_fs.IsNull())
+ return Path::Null();
+
+ char *filename_utf8 = g_strconcat(name, PLAYLIST_FILE_SUFFIX, NULL);
+ const Path filename_fs = Path::FromUTF8(filename_utf8);
+ g_free(filename_utf8);
+ if (filename_fs.IsNull())
+ return Path::Null();
+
+ return Path::Build(playlist_dir_fs, filename_fs);
+}
diff --git a/src/Mapper.hxx b/src/Mapper.hxx
new file mode 100644
index 000000000..f114f27f0
--- /dev/null
+++ b/src/Mapper.hxx
@@ -0,0 +1,146 @@
+/*
+ * 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.
+ */
+
+/*
+ * Maps directory and song objects to file system paths.
+ */
+
+#ifndef MPD_MAPPER_HXX
+#define MPD_MAPPER_HXX
+
+#include "gcc.h"
+#include "gerror.h"
+
+#define PLAYLIST_FILE_SUFFIX ".m3u"
+
+class Path;
+struct Directory;
+struct Song;
+
+bool mapper_init(const char *_music_dir, const char *_playlist_dir,
+ GError **error_r);
+
+void mapper_finish(void);
+
+/**
+ * Return the absolute path of the music directory encoded in UTF-8.
+ */
+gcc_const
+const char *
+mapper_get_music_directory_utf8(void);
+
+/**
+ * Return the absolute path of the music directory encoded in the
+ * filesystem character set.
+ */
+gcc_const
+const Path &
+mapper_get_music_directory_fs(void);
+
+/**
+ * Returns true if a music directory was configured.
+ */
+gcc_const
+static inline bool
+mapper_has_music_directory(void)
+{
+ return mapper_get_music_directory_utf8() != nullptr;
+}
+
+/**
+ * If the specified absolute path points inside the music directory,
+ * this function converts it to a relative path. If not, it returns
+ * the unmodified string pointer.
+ */
+gcc_pure
+const char *
+map_to_relative_path(const char *path_utf8);
+
+/**
+ * Determines the absolute file system path of a relative URI. This
+ * is basically done by converting the URI to the file system charset
+ * and prepending the music directory.
+ */
+gcc_pure
+Path
+map_uri_fs(const char *uri);
+
+/**
+ * Determines the file system path of a directory object.
+ *
+ * @param directory the directory object
+ * @return the path in file system encoding, or nullptr if mapping failed
+ */
+gcc_pure
+Path
+map_directory_fs(const Directory *directory);
+
+/**
+ * Determines the file system path of a directory's child (may be a
+ * sub directory or a song).
+ *
+ * @param directory the parent directory object
+ * @param name the child's name in UTF-8
+ * @return the path in file system encoding, or nullptr if mapping failed
+ */
+gcc_pure
+Path
+map_directory_child_fs(const Directory *directory, const char *name);
+
+/**
+ * Determines the file system path of a song. This must not be a
+ * remote song.
+ *
+ * @param song the song object
+ * @return the path in file system encoding, or nullptr if mapping failed
+ */
+gcc_pure
+Path
+map_song_fs(const Song *song);
+
+/**
+ * Maps a file system path (relative to the music directory or
+ * absolute) to a relative path in UTF-8 encoding.
+ *
+ * @param path_fs a path in file system encoding
+ * @return the relative path in UTF-8, or nullptr if mapping failed
+ */
+gcc_malloc
+char *
+map_fs_to_utf8(const char *path_fs);
+
+/**
+ * Returns the playlist directory.
+ */
+gcc_const
+const Path &
+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().
+ *
+ * @return the path in file system encoding, or nullptr if mapping failed
+ */
+gcc_pure
+Path
+map_spl_utf8_to_fs(const char *name);
+
+#endif
diff --git a/src/MessageCommands.cxx b/src/MessageCommands.cxx
new file mode 100644
index 000000000..4e24a1828
--- /dev/null
+++ b/src/MessageCommands.cxx
@@ -0,0 +1,137 @@
+/*
+ * 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 "MessageCommands.hxx"
+#include "ClientSubscribe.hxx"
+#include "ClientInternal.hxx"
+#include "ClientList.hxx"
+#include "Instance.hxx"
+#include "Main.hxx"
+#include "protocol/Result.hxx"
+#include "protocol/ArgParser.hxx"
+
+#include <set>
+#include <string>
+
+#include <assert.h>
+
+enum command_return
+handle_subscribe(Client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ assert(argc == 2);
+
+ switch (client_subscribe(client, argv[1])) {
+ case CLIENT_SUBSCRIBE_OK:
+ return COMMAND_RETURN_OK;
+
+ case CLIENT_SUBSCRIBE_INVALID:
+ command_error(client, ACK_ERROR_ARG,
+ "invalid channel name");
+ return COMMAND_RETURN_ERROR;
+
+ case CLIENT_SUBSCRIBE_ALREADY:
+ command_error(client, ACK_ERROR_EXIST,
+ "already subscribed to this channel");
+ return COMMAND_RETURN_ERROR;
+
+ case CLIENT_SUBSCRIBE_FULL:
+ command_error(client, ACK_ERROR_EXIST,
+ "subscription list is full");
+ return COMMAND_RETURN_ERROR;
+ }
+
+ /* unreachable */
+ return COMMAND_RETURN_OK;
+}
+
+enum command_return
+handle_unsubscribe(Client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ assert(argc == 2);
+
+ if (client_unsubscribe(client, argv[1]))
+ return COMMAND_RETURN_OK;
+ else {
+ command_error(client, ACK_ERROR_NO_EXIST,
+ "not subscribed to this channel");
+ return COMMAND_RETURN_ERROR;
+ }
+}
+
+enum command_return
+handle_channels(Client *client,
+ G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
+{
+ assert(argc == 1);
+
+ std::set<std::string> channels;
+ for (const auto &c : *instance->client_list)
+ channels.insert(c->subscriptions.begin(),
+ c->subscriptions.end());
+
+ for (const auto &channel : channels)
+ client_printf(client, "channel: %s\n", channel.c_str());
+
+ return COMMAND_RETURN_OK;
+}
+
+enum command_return
+handle_read_messages(Client *client,
+ G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
+{
+ assert(argc == 1);
+
+ while (!client->messages.empty()) {
+ const ClientMessage &msg = client->messages.front();
+
+ client_printf(client, "channel: %s\nmessage: %s\n",
+ msg.GetChannel(), msg.GetMessage());
+ client->messages.pop_front();
+ }
+
+ return COMMAND_RETURN_OK;
+}
+
+enum command_return
+handle_send_message(Client *client,
+ G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
+{
+ assert(argc == 3);
+
+ if (!client_message_valid_channel_name(argv[1])) {
+ command_error(client, ACK_ERROR_ARG,
+ "invalid channel name");
+ return COMMAND_RETURN_ERROR;
+ }
+
+ bool sent = false;
+ const ClientMessage msg(argv[1], argv[2]);
+ for (const auto &c : *instance->client_list)
+ if (client_push_message(c, msg))
+ sent = true;
+
+ if (sent)
+ return COMMAND_RETURN_OK;
+ else {
+ command_error(client, ACK_ERROR_NO_EXIST,
+ "nobody is subscribed to this channel");
+ return COMMAND_RETURN_ERROR;
+ }
+}
diff --git a/src/MessageCommands.hxx b/src/MessageCommands.hxx
new file mode 100644
index 000000000..b10f3d8e8
--- /dev/null
+++ b/src/MessageCommands.hxx
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+#ifndef MPD_MESSAGE_COMMANDS_HXX
+#define MPD_MESSAGE_COMMANDS_HXX
+
+#include "command.h"
+
+class Client;
+
+enum command_return
+handle_subscribe(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_unsubscribe(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_channels(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_read_messages(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_send_message(Client *client, int argc, char *argv[]);
+
+#endif
diff --git a/src/MixerAll.cxx b/src/MixerAll.cxx
new file mode 100644
index 000000000..5a6235de4
--- /dev/null
+++ b/src/MixerAll.cxx
@@ -0,0 +1,177 @@
+/*
+ * 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 "MixerAll.hxx"
+#include "MixerControl.hxx"
+#include "MixerInternal.hxx"
+#include "MixerList.hxx"
+#include "OutputAll.hxx"
+#include "pcm/PcmVolume.hxx"
+#include "OutputInternal.hxx"
+
+#include <glib.h>
+
+#include <assert.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "mixer"
+
+static int
+output_mixer_get_volume(unsigned i)
+{
+ struct audio_output *output;
+ int volume;
+ GError *error = NULL;
+
+ assert(i < audio_output_count());
+
+ output = audio_output_get(i);
+ if (!output->enabled)
+ return -1;
+
+ Mixer *mixer = output->mixer;
+ if (mixer == NULL)
+ return -1;
+
+ volume = mixer_get_volume(mixer, &error);
+ if (volume < 0 && error != NULL) {
+ g_warning("Failed to read mixer for '%s': %s",
+ output->name, error->message);
+ g_error_free(error);
+ }
+
+ return volume;
+}
+
+int
+mixer_all_get_volume(void)
+{
+ unsigned count = audio_output_count(), ok = 0;
+ int volume, total = 0;
+
+ for (unsigned i = 0; i < count; i++) {
+ volume = output_mixer_get_volume(i);
+ if (volume >= 0) {
+ total += volume;
+ ++ok;
+ }
+ }
+
+ if (ok == 0)
+ return -1;
+
+ return total / ok;
+}
+
+static bool
+output_mixer_set_volume(unsigned i, unsigned volume)
+{
+ struct audio_output *output;
+ bool success;
+ GError *error = NULL;
+
+ assert(i < audio_output_count());
+ assert(volume <= 100);
+
+ output = audio_output_get(i);
+ if (!output->enabled)
+ return false;
+
+ Mixer *mixer = output->mixer;
+ if (mixer == NULL)
+ return false;
+
+ success = mixer_set_volume(mixer, volume, &error);
+ if (!success && error != NULL) {
+ g_warning("Failed to set mixer for '%s': %s",
+ output->name, error->message);
+ g_error_free(error);
+ }
+
+ return success;
+}
+
+bool
+mixer_all_set_volume(unsigned volume)
+{
+ bool success = false;
+ unsigned count = audio_output_count();
+
+ assert(volume <= 100);
+
+ for (unsigned i = 0; i < count; i++)
+ success = output_mixer_set_volume(i, volume)
+ || success;
+
+ return success;
+}
+
+static int
+output_mixer_get_software_volume(unsigned i)
+{
+ struct audio_output *output;
+
+ assert(i < audio_output_count());
+
+ output = audio_output_get(i);
+ if (!output->enabled)
+ return -1;
+
+ Mixer *mixer = output->mixer;
+ if (mixer == NULL || !mixer->IsPlugin(software_mixer_plugin))
+ return -1;
+
+ return mixer_get_volume(mixer, NULL);
+}
+
+int
+mixer_all_get_software_volume(void)
+{
+ unsigned count = audio_output_count(), ok = 0;
+ int volume, total = 0;
+
+ for (unsigned i = 0; i < count; i++) {
+ volume = output_mixer_get_software_volume(i);
+ if (volume >= 0) {
+ total += volume;
+ ++ok;
+ }
+ }
+
+ if (ok == 0)
+ return -1;
+
+ return total / ok;
+}
+
+void
+mixer_all_set_software_volume(unsigned volume)
+{
+ unsigned count = audio_output_count();
+
+ assert(volume <= PCM_VOLUME_1);
+
+ for (unsigned i = 0; i < count; i++) {
+ struct audio_output *output = audio_output_get(i);
+ if (output->mixer != NULL &&
+ output->mixer->plugin == &software_mixer_plugin)
+ mixer_set_volume(output->mixer, volume, NULL);
+ }
+}
diff --git a/src/MixerAll.hxx b/src/MixerAll.hxx
new file mode 100644
index 000000000..23350a843
--- /dev/null
+++ b/src/MixerAll.hxx
@@ -0,0 +1,60 @@
+/*
+ * 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
+ *
+ * Functions which affect the mixers of all audio outputs.
+ */
+
+#ifndef MPD_MIXER_ALL_HXX
+#define MPD_MIXER_ALL_HXX
+
+/**
+ * Returns the average volume of all available mixers (range 0..100).
+ * Returns -1 if no mixer can be queried.
+ */
+int
+mixer_all_get_volume(void);
+
+/**
+ * Sets the volume on all available mixers.
+ *
+ * @param volume the volume (range 0..100)
+ * @return true on success, false on failure
+ */
+bool
+mixer_all_set_volume(unsigned volume);
+
+/**
+ * Similar to mixer_all_get_volume(), but gets the volume only for
+ * software mixers. See #software_mixer_plugin. This function fails
+ * if no software mixer is configured.
+ */
+int
+mixer_all_get_software_volume(void);
+
+/**
+ * Similar to mixer_all_set_volume(), but sets the volume only for
+ * software mixers. See #software_mixer_plugin. This function cannot
+ * fail, because the underlying software mixers cannot fail either.
+ */
+void
+mixer_all_set_software_volume(unsigned volume);
+
+#endif
diff --git a/src/MixerControl.cxx b/src/MixerControl.cxx
new file mode 100644
index 000000000..bbb3ede2c
--- /dev/null
+++ b/src/MixerControl.cxx
@@ -0,0 +1,170 @@
+/*
+ * 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 "MixerControl.hxx"
+#include "MixerInternal.hxx"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <stddef.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "mixer"
+
+Mixer *
+mixer_new(const struct mixer_plugin *plugin, void *ao,
+ const config_param &param,
+ GError **error_r)
+{
+ Mixer *mixer;
+
+ assert(plugin != NULL);
+
+ mixer = plugin->init(ao, param, error_r);
+
+ assert(mixer == NULL || mixer->IsPlugin(*plugin));
+
+ return mixer;
+}
+
+void
+mixer_free(Mixer *mixer)
+{
+ assert(mixer != NULL);
+ assert(mixer->plugin != NULL);
+
+ /* mixers with the "global" flag set might still be open at
+ this point (see mixer_auto_close()) */
+ mixer_close(mixer);
+
+ mixer->plugin->finish(mixer);
+}
+
+bool
+mixer_open(Mixer *mixer, GError **error_r)
+{
+ bool success;
+
+ assert(mixer != NULL);
+ assert(mixer->plugin != NULL);
+
+ const ScopeLock protect(mixer->mutex);
+
+ if (mixer->open)
+ success = true;
+ else if (mixer->plugin->open == NULL)
+ success = mixer->open = true;
+ else
+ success = mixer->open = mixer->plugin->open(mixer, error_r);
+
+ mixer->failed = !success;
+
+ return success;
+}
+
+static void
+mixer_close_internal(Mixer *mixer)
+{
+ assert(mixer != NULL);
+ assert(mixer->plugin != NULL);
+ assert(mixer->open);
+
+ if (mixer->plugin->close != NULL)
+ mixer->plugin->close(mixer);
+
+ mixer->open = false;
+}
+
+void
+mixer_close(Mixer *mixer)
+{
+ assert(mixer != NULL);
+ assert(mixer->plugin != NULL);
+
+ const ScopeLock protect(mixer->mutex);
+
+ if (mixer->open)
+ mixer_close_internal(mixer);
+}
+
+void
+mixer_auto_close(Mixer *mixer)
+{
+ if (!mixer->plugin->global)
+ mixer_close(mixer);
+}
+
+/*
+ * Close the mixer due to failure. The mutex must be locked before
+ * calling this function.
+ */
+static void
+mixer_failed(Mixer *mixer)
+{
+ assert(mixer->open);
+
+ mixer_close_internal(mixer);
+
+ mixer->failed = true;
+}
+
+int
+mixer_get_volume(Mixer *mixer, GError **error_r)
+{
+ int volume;
+
+ assert(mixer != NULL);
+
+ if (mixer->plugin->global && !mixer->failed &&
+ !mixer_open(mixer, error_r))
+ return -1;
+
+ const ScopeLock protect(mixer->mutex);
+
+ if (mixer->open) {
+ GError *error = NULL;
+
+ volume = mixer->plugin->get_volume(mixer, &error);
+ if (volume < 0 && error != NULL) {
+ g_propagate_error(error_r, error);
+ mixer_failed(mixer);
+ }
+ } else
+ volume = -1;
+
+ return volume;
+}
+
+bool
+mixer_set_volume(Mixer *mixer, unsigned volume, GError **error_r)
+{
+ assert(mixer != NULL);
+ assert(volume <= 100);
+
+ if (mixer->plugin->global && !mixer->failed &&
+ !mixer_open(mixer, error_r))
+ return false;
+
+ const ScopeLock protect(mixer->mutex);
+
+ return mixer->open &&
+ mixer->plugin->set_volume(mixer, volume, error_r);
+}
diff --git a/src/MixerControl.hxx b/src/MixerControl.hxx
new file mode 100644
index 000000000..e7b65d6e8
--- /dev/null
+++ b/src/MixerControl.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.
+ */
+
+/** \file
+ *
+ * Functions which manipulate a #mixer object.
+ */
+
+#ifndef MPD_MIXER_CONTROL_HXX
+#define MPD_MIXER_CONTROL_HXX
+
+#include "gerror.h"
+
+class Mixer;
+struct mixer_plugin;
+struct config_param;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+Mixer *
+mixer_new(const struct mixer_plugin *plugin, void *ao,
+ const config_param &param,
+ GError **error_r);
+
+void
+mixer_free(Mixer *mixer);
+
+bool
+mixer_open(Mixer *mixer, GError **error_r);
+
+void
+mixer_close(Mixer *mixer);
+
+/**
+ * Close the mixer unless the plugin's "global" flag is set. This is
+ * called when the #audio_output is closed.
+ */
+void
+mixer_auto_close(Mixer *mixer);
+
+int
+mixer_get_volume(Mixer *mixer, GError **error_r);
+
+bool
+mixer_set_volume(Mixer *mixer, unsigned volume, GError **error_r);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/MixerInternal.hxx b/src/MixerInternal.hxx
new file mode 100644
index 000000000..e421a34b4
--- /dev/null
+++ b/src/MixerInternal.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_MIXER_INTERNAL_HXX
+#define MPD_MIXER_INTERNAL_HXX
+
+#include "MixerPlugin.hxx"
+#include "MixerList.hxx"
+#include "thread/Mutex.hxx"
+
+class Mixer {
+public:
+ const struct mixer_plugin *plugin;
+
+ /**
+ * This mutex protects all of the mixer struct, including its
+ * implementation, so plugins don't have to deal with that.
+ */
+ Mutex mutex;
+
+ /**
+ * Is the mixer device currently open?
+ */
+ bool open;
+
+ /**
+ * Has this mixer failed, and should not be reopened
+ * automatically?
+ */
+ bool failed;
+
+public:
+ Mixer(const mixer_plugin &_plugin)
+ :plugin(&_plugin),
+ open(false),
+ failed(false) {}
+
+ bool IsPlugin(const mixer_plugin &other) const {
+ return plugin == &other;
+ }
+};
+
+#endif
diff --git a/src/MixerList.hxx b/src/MixerList.hxx
new file mode 100644
index 000000000..440f442ba
--- /dev/null
+++ b/src/MixerList.hxx
@@ -0,0 +1,35 @@
+/*
+ * 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
+ *
+ * This header provides "extern" declarations for all mixer plugins.
+ */
+
+#ifndef MPD_MIXER_LIST_HXX
+#define MPD_MIXER_LIST_HXX
+
+extern const struct mixer_plugin software_mixer_plugin;
+extern const struct mixer_plugin alsa_mixer_plugin;
+extern const struct mixer_plugin oss_mixer_plugin;
+extern const struct mixer_plugin roar_mixer_plugin;
+extern const struct mixer_plugin pulse_mixer_plugin;
+extern const struct mixer_plugin winmm_mixer_plugin;
+
+#endif
diff --git a/src/MixerPlugin.hxx b/src/MixerPlugin.hxx
new file mode 100644
index 000000000..e80ae094e
--- /dev/null
+++ b/src/MixerPlugin.hxx
@@ -0,0 +1,96 @@
+/*
+ * 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
+ *
+ * This header declares the mixer_plugin class. It should not be
+ * included directly; use MixerInternal.hxx instead in mixer
+ * implementations.
+ */
+
+#ifndef MPD_MIXER_PLUGIN_HXX
+#define MPD_MIXER_PLUGIN_HXX
+
+#include "gerror.h"
+
+struct config_param;
+class Mixer;
+
+struct mixer_plugin {
+ /**
+ * Alocates and configures a mixer device.
+ *
+ * @param ao the pointer returned by audio_output_plugin.init
+ * @param param the configuration section
+ * @param error_r location to store the error occurring, or
+ * NULL to ignore errors
+ * @return a mixer object, or NULL on error
+ */
+ Mixer *(*init)(void *ao, const config_param &param,
+ GError **error_r);
+
+ /**
+ * Finish and free mixer data
+ */
+ void (*finish)(Mixer *data);
+
+ /**
+ * Open mixer device
+ *
+ * @param error_r location to store the error occurring, or
+ * NULL to ignore errors
+ * @return true on success, false on error
+ */
+ bool (*open)(Mixer *data, GError **error_r);
+
+ /**
+ * Close mixer device
+ */
+ void (*close)(Mixer *data);
+
+ /**
+ * Reads the current volume.
+ *
+ * @param error_r location to store the error occurring, or
+ * NULL to ignore errors
+ * @return the current volume (0..100 including) or -1 if
+ * unavailable or on error (error_r set, mixer will be closed)
+ */
+ int (*get_volume)(Mixer *mixer, GError **error_r);
+
+ /**
+ * Sets the volume.
+ *
+ * @param error_r location to store the error occurring, or
+ * NULL to ignore errors
+ * @param volume the new volume (0..100 including)
+ * @return true on success, false on error
+ */
+ bool (*set_volume)(Mixer *mixer, unsigned volume,
+ GError **error_r);
+
+ /**
+ * If true, then the mixer is automatically opened, even if
+ * its audio output is not open. If false, then the mixer is
+ * disabled as long as its audio output is closed.
+ */
+ bool global;
+};
+
+#endif
diff --git a/src/MixerType.cxx b/src/MixerType.cxx
new file mode 100644
index 000000000..435079790
--- /dev/null
+++ b/src/MixerType.cxx
@@ -0,0 +1,39 @@
+/*
+ * 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 "MixerType.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+enum mixer_type
+mixer_type_parse(const char *input)
+{
+ assert(input != NULL);
+
+ if (strcmp(input, "none") == 0 || strcmp(input, "disabled") == 0)
+ return MIXER_TYPE_NONE;
+ else if (strcmp(input, "hardware") == 0)
+ return MIXER_TYPE_HARDWARE;
+ else if (strcmp(input, "software") == 0)
+ return MIXER_TYPE_SOFTWARE;
+ else
+ return MIXER_TYPE_UNKNOWN;
+}
diff --git a/src/MixerType.hxx b/src/MixerType.hxx
new file mode 100644
index 000000000..320a36c04
--- /dev/null
+++ b/src/MixerType.hxx
@@ -0,0 +1,47 @@
+/*
+ * 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_MIXER_TYPE_HXX
+#define MPD_MIXER_TYPE_HXX
+
+enum mixer_type {
+ /** parser error */
+ MIXER_TYPE_UNKNOWN,
+
+ /** mixer disabled */
+ MIXER_TYPE_NONE,
+
+ /** software mixer with pcm_volume() */
+ MIXER_TYPE_SOFTWARE,
+
+ /** hardware mixer (output's plugin) */
+ MIXER_TYPE_HARDWARE,
+};
+
+/**
+ * Parses a "mixer_type" setting from the configuration file.
+ *
+ * @param input the configured string value; must not be NULL
+ * @return a #mixer_type value; MIXER_TYPE_UNKNOWN means #input could
+ * not be parsed
+ */
+enum mixer_type
+mixer_type_parse(const char *input);
+
+#endif
diff --git a/src/MusicBuffer.cxx b/src/MusicBuffer.cxx
new file mode 100644
index 000000000..ea03fc0b9
--- /dev/null
+++ b/src/MusicBuffer.cxx
@@ -0,0 +1,79 @@
+/*
+ * 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 "MusicBuffer.hxx"
+#include "MusicChunk.hxx"
+#include "thread/Mutex.hxx"
+#include "util/SliceBuffer.hxx"
+#include "mpd_error.h"
+
+#include <assert.h>
+
+struct music_buffer : public SliceBuffer<music_chunk> {
+ /** a mutex which protects #available */
+ Mutex mutex;
+
+ music_buffer(unsigned num_chunks)
+ :SliceBuffer(num_chunks) {
+ if (IsOOM())
+ MPD_ERROR("Failed to allocate buffer");
+ }
+};
+
+struct music_buffer *
+music_buffer_new(unsigned num_chunks)
+{
+ return new music_buffer(num_chunks);
+}
+
+void
+music_buffer_free(struct music_buffer *buffer)
+{
+ delete buffer;
+}
+
+unsigned
+music_buffer_size(const struct music_buffer *buffer)
+{
+ return buffer->GetCapacity();
+}
+
+struct music_chunk *
+music_buffer_allocate(struct music_buffer *buffer)
+{
+ const ScopeLock protect(buffer->mutex);
+ return buffer->Allocate();
+}
+
+void
+music_buffer_return(struct music_buffer *buffer, struct music_chunk *chunk)
+{
+ assert(buffer != NULL);
+ assert(chunk != NULL);
+
+ const ScopeLock protect(buffer->mutex);
+
+ if (chunk->other != nullptr) {
+ assert(chunk->other->other == nullptr);
+ buffer->Free(chunk->other);
+ }
+
+ buffer->Free(chunk);
+}
diff --git a/src/MusicBuffer.hxx b/src/MusicBuffer.hxx
new file mode 100644
index 000000000..cc03dfcb3
--- /dev/null
+++ b/src/MusicBuffer.hxx
@@ -0,0 +1,67 @@
+/*
+ * 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_MUSIC_BUFFER_HXX
+#define MPD_MUSIC_BUFFER_HXX
+
+/**
+ * An allocator for #music_chunk objects.
+ */
+struct music_buffer;
+
+/**
+ * Creates a new #music_buffer object.
+ *
+ * @param num_chunks the number of #music_chunk reserved in this
+ * buffer
+ */
+struct music_buffer *
+music_buffer_new(unsigned num_chunks);
+
+/**
+ * Frees the #music_buffer object
+ */
+void
+music_buffer_free(struct music_buffer *buffer);
+
+/**
+ * Returns the total number of reserved chunks in this buffer. This
+ * is the same value which was passed to the constructor
+ * music_buffer_new().
+ */
+unsigned
+music_buffer_size(const struct music_buffer *buffer);
+
+/**
+ * Allocates a chunk from the buffer. When it is not used anymore,
+ * call music_buffer_return().
+ *
+ * @return an empty chunk or NULL if there are no chunks available
+ */
+struct music_chunk *
+music_buffer_allocate(struct music_buffer *buffer);
+
+/**
+ * Returns a chunk to the buffer. It can be reused by
+ * music_buffer_allocate() then.
+ */
+void
+music_buffer_return(struct music_buffer *buffer, struct music_chunk *chunk);
+
+#endif
diff --git a/src/MusicChunk.cxx b/src/MusicChunk.cxx
new file mode 100644
index 000000000..fb9019988
--- /dev/null
+++ b/src/MusicChunk.cxx
@@ -0,0 +1,82 @@
+/*
+ * 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 "MusicChunk.hxx"
+#include "AudioFormat.hxx"
+#include "Tag.hxx"
+
+#include <assert.h>
+
+music_chunk::~music_chunk()
+{
+ delete tag;
+}
+
+#ifndef NDEBUG
+bool
+music_chunk::CheckFormat(const AudioFormat other_format) const
+{
+ assert(other_format.IsValid());
+
+ return length == 0 || audio_format == other_format;
+}
+#endif
+
+void *
+music_chunk::Write(const AudioFormat af,
+ float data_time, uint16_t _bit_rate,
+ size_t *max_length_r)
+{
+ assert(CheckFormat(af));
+ assert(length == 0 || audio_format.IsValid());
+
+ if (length == 0) {
+ /* if the chunk is empty, nobody has set bitRate and
+ times yet */
+
+ bit_rate = _bit_rate;
+ times = data_time;
+ }
+
+ const size_t frame_size = af.GetFrameSize();
+ size_t num_frames = (sizeof(data) - length) / frame_size;
+ if (num_frames == 0)
+ return NULL;
+
+#ifndef NDEBUG
+ audio_format = af;
+#endif
+
+ *max_length_r = num_frames * frame_size;
+ return data + length;
+}
+
+bool
+music_chunk::Expand(const AudioFormat af, size_t _length)
+{
+ const size_t frame_size = af.GetFrameSize();
+
+ assert(length + _length <= sizeof(data));
+ assert(audio_format == af);
+
+ length += _length;
+
+ return length + frame_size > sizeof(data);
+}
diff --git a/src/MusicChunk.hxx b/src/MusicChunk.hxx
new file mode 100644
index 000000000..23c3b7423
--- /dev/null
+++ b/src/MusicChunk.hxx
@@ -0,0 +1,154 @@
+/*
+ * 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_MUSIC_CHUNK_HXX
+#define MPD_MUSIC_CHUNK_HXX
+
+#include "replay_gain_info.h"
+
+#ifndef NDEBUG
+#include "AudioFormat.hxx"
+#endif
+
+#include <stdint.h>
+#include <stddef.h>
+
+enum {
+ CHUNK_SIZE = 4096,
+};
+
+struct AudioFormat;
+struct Tag;
+
+/**
+ * A chunk of music data. Its format is defined by the
+ * music_pipe_append() caller.
+ */
+struct music_chunk {
+ /** the next chunk in a linked list */
+ struct music_chunk *next;
+
+ /**
+ * An optional chunk which should be mixed into this chunk.
+ * This is used for cross-fading.
+ */
+ struct music_chunk *other;
+
+ /**
+ * The current mix ratio for cross-fading: 1.0 means play 100%
+ * of this chunk, 0.0 means play 100% of the "other" chunk.
+ */
+ float mix_ratio;
+
+ /** number of bytes stored in this chunk */
+ uint16_t length;
+
+ /** current bit rate of the source file */
+ uint16_t bit_rate;
+
+ /** the time stamp within the song */
+ float times;
+
+ /**
+ * An optional tag associated with this chunk (and the
+ * following chunks); appears at song boundaries. The tag
+ * object is owned by this chunk, and must be freed when this
+ * chunk is deinitialized in music_chunk_free()
+ */
+ Tag *tag;
+
+ /**
+ * Replay gain information associated with this chunk.
+ * Only valid if the serial is not 0.
+ */
+ struct replay_gain_info replay_gain_info;
+
+ /**
+ * A serial number for checking if replay gain info has
+ * changed since the last chunk. The magic value 0 indicates
+ * that there is no replay gain info available.
+ */
+ unsigned replay_gain_serial;
+
+ /** the data (probably PCM) */
+ char data[CHUNK_SIZE];
+
+#ifndef NDEBUG
+ AudioFormat audio_format;
+#endif
+
+ music_chunk()
+ :other(nullptr),
+ length(0),
+ tag(nullptr),
+ replay_gain_serial(0) {}
+
+ ~music_chunk();
+
+ bool IsEmpty() const {
+ return length == 0 && tag == nullptr;
+ }
+
+#ifndef NDEBUG
+ /**
+ * Checks if the audio format if the chunk is equal to the
+ * specified audio_format.
+ */
+ gcc_pure
+ bool CheckFormat(AudioFormat audio_format) const;
+#endif
+
+ /**
+ * Prepares appending to the music chunk. Returns a buffer
+ * where you may write into. After you are finished, call
+ * music_chunk_expand().
+ *
+ * @param chunk the music_chunk object
+ * @param audio_format the audio format for the appended data;
+ * must stay the same for the life cycle of this chunk
+ * @param data_time the time within the song
+ * @param bit_rate the current bit rate of the source file
+ * @param max_length_r the maximum write length is returned
+ * here
+ * @return a writable buffer, or NULL if the chunk is full
+ */
+ void *Write(AudioFormat af,
+ float data_time, uint16_t bit_rate,
+ size_t *max_length_r);
+
+ /**
+ * Increases the length of the chunk after the caller has written to
+ * the buffer returned by music_chunk_write().
+ *
+ * @param chunk the music_chunk object
+ * @param audio_format the audio format for the appended data; must
+ * stay the same for the life cycle of this chunk
+ * @param length the number of bytes which were appended
+ * @return true if the chunk is full
+ */
+ bool Expand(AudioFormat af, size_t length);
+};
+
+void
+music_chunk_init(struct music_chunk *chunk);
+
+void
+music_chunk_free(struct music_chunk *chunk);
+
+#endif
diff --git a/src/MusicPipe.cxx b/src/MusicPipe.cxx
new file mode 100644
index 000000000..5da56cd0c
--- /dev/null
+++ b/src/MusicPipe.cxx
@@ -0,0 +1,178 @@
+/*
+ * 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.
+ */
+
+#include "config.h"
+#include "MusicPipe.hxx"
+#include "MusicBuffer.hxx"
+#include "MusicChunk.hxx"
+#include "thread/Mutex.hxx"
+
+#include <glib.h>
+
+#include <assert.h>
+
+struct music_pipe {
+ /** the first chunk */
+ struct music_chunk *head;
+
+ /** a pointer to the tail of the chunk */
+ struct music_chunk **tail_r;
+
+ /** the current number of chunks */
+ unsigned size;
+
+ /** a mutex which protects #head and #tail_r */
+ mutable Mutex mutex;
+
+#ifndef NDEBUG
+ AudioFormat audio_format;
+#endif
+
+ music_pipe()
+ :head(nullptr), tail_r(&head), size(0) {
+#ifndef NDEBUG
+ audio_format.Clear();
+#endif
+ }
+
+ ~music_pipe() {
+ assert(head == nullptr);
+ assert(tail_r == &head);
+ }
+};
+
+struct music_pipe *
+music_pipe_new(void)
+{
+ return new music_pipe();
+}
+
+void
+music_pipe_free(struct music_pipe *mp)
+{
+ delete mp;
+}
+
+#ifndef NDEBUG
+
+bool
+music_pipe_check_format(const struct music_pipe *pipe,
+ const AudioFormat audio_format)
+{
+ assert(pipe != NULL);
+
+ return !pipe->audio_format.IsDefined() ||
+ pipe->audio_format == audio_format;
+}
+
+bool
+music_pipe_contains(const struct music_pipe *mp,
+ const struct music_chunk *chunk)
+{
+ const ScopeLock protect(mp->mutex);
+
+ for (const struct music_chunk *i = mp->head;
+ i != NULL; i = i->next)
+ if (i == chunk)
+ return true;
+
+ return false;
+}
+
+#endif
+
+const struct music_chunk *
+music_pipe_peek(const struct music_pipe *mp)
+{
+ return mp->head;
+}
+
+struct music_chunk *
+music_pipe_shift(struct music_pipe *mp)
+{
+ const ScopeLock protect(mp->mutex);
+
+ struct music_chunk *chunk = mp->head;
+ if (chunk != NULL) {
+ assert(!chunk->IsEmpty());
+
+ mp->head = chunk->next;
+ --mp->size;
+
+ if (mp->head == NULL) {
+ assert(mp->size == 0);
+ assert(mp->tail_r == &chunk->next);
+
+ mp->tail_r = &mp->head;
+ } else {
+ assert(mp->size > 0);
+ assert(mp->tail_r != &chunk->next);
+ }
+
+#ifndef NDEBUG
+ /* poison the "next" reference */
+ chunk->next = (struct music_chunk *)(void *)0x01010101;
+
+ if (mp->size == 0)
+ mp->audio_format.Clear();
+#endif
+ }
+
+ return chunk;
+}
+
+void
+music_pipe_clear(struct music_pipe *mp, struct music_buffer *buffer)
+{
+ struct music_chunk *chunk;
+
+ while ((chunk = music_pipe_shift(mp)) != NULL)
+ music_buffer_return(buffer, chunk);
+}
+
+void
+music_pipe_push(struct music_pipe *mp, struct music_chunk *chunk)
+{
+ assert(!chunk->IsEmpty());
+ assert(chunk->length == 0 || chunk->audio_format.IsValid());
+
+ const ScopeLock protect(mp->mutex);
+
+ assert(mp->size > 0 || !mp->audio_format.IsDefined());
+ assert(!mp->audio_format.IsDefined() ||
+ chunk->CheckFormat(mp->audio_format));
+
+#ifndef NDEBUG
+ if (!mp->audio_format.IsDefined() && chunk->length > 0)
+ mp->audio_format = chunk->audio_format;
+#endif
+
+ chunk->next = NULL;
+ *mp->tail_r = chunk;
+ mp->tail_r = &chunk->next;
+
+ ++mp->size;
+}
+
+unsigned
+music_pipe_size(const struct music_pipe *mp)
+{
+ const ScopeLock protect(mp->mutex);
+ return mp->size;
+}
diff --git a/src/MusicPipe.hxx b/src/MusicPipe.hxx
new file mode 100644
index 000000000..39ad8e981
--- /dev/null
+++ b/src/MusicPipe.hxx
@@ -0,0 +1,112 @@
+/*
+ * 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_PIPE_H
+#define MPD_PIPE_H
+
+#include "gcc.h"
+
+#ifndef NDEBUG
+struct AudioFormat;
+#endif
+
+struct music_chunk;
+struct music_buffer;
+
+/**
+ * A queue of #music_chunk objects. One party appends chunks at the
+ * tail, and the other consumes them from the head.
+ */
+struct music_pipe;
+
+/**
+ * Creates a new #music_pipe object. It is empty.
+ */
+gcc_malloc
+struct music_pipe *
+music_pipe_new(void);
+
+/**
+ * Frees the object. It must be empty now.
+ */
+void
+music_pipe_free(struct music_pipe *mp);
+
+#ifndef NDEBUG
+
+/**
+ * Checks if the audio format if the chunk is equal to the specified
+ * audio_format.
+ */
+bool
+music_pipe_check_format(const struct music_pipe *pipe,
+ AudioFormat audio_format);
+
+/**
+ * Checks if the specified chunk is enqueued in the music pipe.
+ */
+bool
+music_pipe_contains(const struct music_pipe *mp,
+ const struct music_chunk *chunk);
+
+#endif
+
+/**
+ * Returns the first #music_chunk from the pipe. Returns NULL if the
+ * pipe is empty.
+ */
+gcc_pure
+const struct music_chunk *
+music_pipe_peek(const struct music_pipe *mp);
+
+/**
+ * Removes the first chunk from the head, and returns it.
+ */
+struct music_chunk *
+music_pipe_shift(struct music_pipe *mp);
+
+/**
+ * Clears the whole pipe and returns the chunks to the buffer.
+ *
+ * @param buffer the buffer object to return the chunks to
+ */
+void
+music_pipe_clear(struct music_pipe *mp, struct music_buffer *buffer);
+
+/**
+ * Pushes a chunk to the tail of the pipe.
+ */
+void
+music_pipe_push(struct music_pipe *mp, struct music_chunk *chunk);
+
+/**
+ * Returns the number of chunks currently in this pipe.
+ */
+gcc_pure
+unsigned
+music_pipe_size(const struct music_pipe *mp);
+
+gcc_pure
+static inline bool
+music_pipe_empty(const struct music_pipe *mp)
+{
+ return music_pipe_size(mp) == 0;
+}
+
+#endif
diff --git a/src/OtherCommands.cxx b/src/OtherCommands.cxx
new file mode 100644
index 000000000..0137cfd57
--- /dev/null
+++ b/src/OtherCommands.cxx
@@ -0,0 +1,315 @@
+/*
+ * 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 "OtherCommands.hxx"
+#include "DatabaseCommands.hxx"
+#include "CommandError.hxx"
+#include "UpdateGlue.hxx"
+#include "Directory.hxx"
+#include "Song.hxx"
+#include "SongPrint.hxx"
+#include "TagPrint.hxx"
+#include "TimePrint.hxx"
+#include "Mapper.hxx"
+#include "DecoderPrint.hxx"
+#include "protocol/ArgParser.hxx"
+#include "protocol/Result.hxx"
+#include "ls.hxx"
+#include "Volume.hxx"
+#include "util/UriUtil.hxx"
+#include "fs/Path.hxx"
+
+extern "C" {
+#include "stats.h"
+}
+
+#include "Permission.hxx"
+#include "PlaylistFile.hxx"
+#include "ClientFile.hxx"
+#include "ClientInternal.hxx"
+#include "Idle.hxx"
+
+#ifdef ENABLE_SQLITE
+#include "StickerDatabase.hxx"
+#endif
+
+#include <assert.h>
+#include <string.h>
+
+static void
+print_spl_list(Client *client, const PlaylistVector &list)
+{
+ for (const auto &i : list) {
+ client_printf(client, "playlist: %s\n", i.name.c_str());
+
+ if (i.mtime > 0)
+ time_print(client, "Last-Modified", i.mtime);
+ }
+}
+
+enum command_return
+handle_urlhandlers(Client *client,
+ G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
+{
+ if (client_is_local(client))
+ client_puts(client, "handler: file://\n");
+ print_supported_uri_schemes(client);
+ return COMMAND_RETURN_OK;
+}
+
+enum command_return
+handle_decoders(Client *client,
+ G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
+{
+ decoder_list_print(client);
+ return COMMAND_RETURN_OK;
+}
+
+enum command_return
+handle_tagtypes(Client *client,
+ G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
+{
+ tag_print_types(client);
+ return COMMAND_RETURN_OK;
+}
+
+enum command_return
+handle_kill(G_GNUC_UNUSED Client *client,
+ G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
+{
+ return COMMAND_RETURN_KILL;
+}
+
+enum command_return
+handle_close(G_GNUC_UNUSED Client *client,
+ G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
+{
+ return COMMAND_RETURN_CLOSE;
+}
+
+enum command_return
+handle_lsinfo(Client *client, int argc, char *argv[])
+{
+ const char *uri;
+
+ if (argc == 2)
+ uri = argv[1];
+ else
+ /* default is root directory */
+ uri = "";
+
+ if (strncmp(uri, "file:///", 8) == 0) {
+ /* print information about an arbitrary local file */
+ const char *path_utf8 = uri + 7;
+ const Path path_fs = Path::FromUTF8(path_utf8);
+
+ if (path_fs.IsNull()) {
+ command_error(client, ACK_ERROR_NO_EXIST,
+ "unsupported file name");
+ return COMMAND_RETURN_ERROR;
+ }
+
+ GError *error = NULL;
+ if (!client_allow_file(client, path_fs, &error))
+ return print_error(client, error);
+
+ Song *song = Song::LoadFile(path_utf8, nullptr);
+ if (song == NULL) {
+ command_error(client, ACK_ERROR_NO_EXIST,
+ "No such file");
+ return COMMAND_RETURN_ERROR;
+ }
+
+ song_print_info(client, song);
+ song->Free();
+ return COMMAND_RETURN_OK;
+ }
+
+ enum command_return result = handle_lsinfo2(client, argc, argv);
+ if (result != COMMAND_RETURN_OK)
+ return result;
+
+ if (isRootDirectory(uri)) {
+ const auto &list = ListPlaylistFiles(NULL);
+ print_spl_list(client, list);
+ }
+
+ return COMMAND_RETURN_OK;
+}
+
+enum command_return
+handle_update(Client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ const char *path = NULL;
+ unsigned ret;
+
+ assert(argc <= 2);
+ if (argc == 2) {
+ path = argv[1];
+
+ if (*path == 0 || strcmp(path, "/") == 0)
+ /* backwards compatibility with MPD 0.15 */
+ path = NULL;
+ else if (!uri_safe_local(path)) {
+ command_error(client, ACK_ERROR_ARG,
+ "Malformed path");
+ return COMMAND_RETURN_ERROR;
+ }
+ }
+
+ ret = update_enqueue(path, false);
+ if (ret > 0) {
+ client_printf(client, "updating_db: %i\n", ret);
+ return COMMAND_RETURN_OK;
+ } else {
+ command_error(client, ACK_ERROR_UPDATE_ALREADY,
+ "already updating");
+ return COMMAND_RETURN_ERROR;
+ }
+}
+
+enum command_return
+handle_rescan(Client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ const char *path = NULL;
+ unsigned ret;
+
+ assert(argc <= 2);
+ if (argc == 2) {
+ path = argv[1];
+
+ if (!uri_safe_local(path)) {
+ command_error(client, ACK_ERROR_ARG,
+ "Malformed path");
+ return COMMAND_RETURN_ERROR;
+ }
+ }
+
+ ret = update_enqueue(path, true);
+ if (ret > 0) {
+ client_printf(client, "updating_db: %i\n", ret);
+ return COMMAND_RETURN_OK;
+ } else {
+ command_error(client, ACK_ERROR_UPDATE_ALREADY,
+ "already updating");
+ return COMMAND_RETURN_ERROR;
+ }
+}
+
+enum command_return
+handle_setvol(Client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ unsigned level;
+ bool success;
+
+ if (!check_unsigned(client, &level, argv[1]))
+ return COMMAND_RETURN_ERROR;
+
+ if (level > 100) {
+ command_error(client, ACK_ERROR_ARG, "Invalid volume value");
+ return COMMAND_RETURN_ERROR;
+ }
+
+ success = volume_level_change(level);
+ if (!success) {
+ command_error(client, ACK_ERROR_SYSTEM,
+ "problems setting volume");
+ return COMMAND_RETURN_ERROR;
+ }
+
+ return COMMAND_RETURN_OK;
+}
+
+enum command_return
+handle_stats(Client *client,
+ G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
+{
+ stats_print(client);
+ return COMMAND_RETURN_OK;
+}
+
+enum command_return
+handle_ping(G_GNUC_UNUSED Client *client,
+ G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
+{
+ return COMMAND_RETURN_OK;
+}
+
+enum command_return
+handle_password(Client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ unsigned permission = 0;
+
+ if (getPermissionFromPassword(argv[1], &permission) < 0) {
+ command_error(client, ACK_ERROR_PASSWORD, "incorrect password");
+ return COMMAND_RETURN_ERROR;
+ }
+
+ client_set_permission(client, permission);
+
+ return COMMAND_RETURN_OK;
+}
+
+enum command_return
+handle_config(Client *client,
+ G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
+{
+ if (!client_is_local(client)) {
+ command_error(client, ACK_ERROR_PERMISSION,
+ "Command only permitted to local clients");
+ return COMMAND_RETURN_ERROR;
+ }
+
+ const char *path = mapper_get_music_directory_utf8();
+ if (path != NULL)
+ client_printf(client, "music_directory: %s\n", path);
+
+ return COMMAND_RETURN_OK;
+}
+
+enum command_return
+handle_idle(Client *client,
+ G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
+{
+ unsigned flags = 0, j;
+ int i;
+ const char *const* idle_names;
+
+ idle_names = idle_get_names();
+ for (i = 1; i < argc; ++i) {
+ if (!argv[i])
+ continue;
+
+ for (j = 0; idle_names[j]; ++j) {
+ if (!g_ascii_strcasecmp(argv[i], idle_names[j])) {
+ flags |= (1 << j);
+ }
+ }
+ }
+
+ /* No argument means that the client wants to receive everything */
+ if (flags == 0)
+ flags = ~0;
+
+ /* enable "idle" mode on this client */
+ client->IdleWait(flags);
+
+ return COMMAND_RETURN_IDLE;
+}
diff --git a/src/OtherCommands.hxx b/src/OtherCommands.hxx
new file mode 100644
index 000000000..564ad38e7
--- /dev/null
+++ b/src/OtherCommands.hxx
@@ -0,0 +1,69 @@
+/*
+ * 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.
+ */
+
+#ifndef MPD_OTHER_COMMANDS_HXX
+#define MPD_OTHER_COMMANDS_HXX
+
+#include "command.h"
+
+class Client;
+
+enum command_return
+handle_urlhandlers(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_decoders(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_tagtypes(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_kill(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_close(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_lsinfo(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_update(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_rescan(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_setvol(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_stats(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_ping(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_password(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_config(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_idle(Client *client, int argc, char *argv[]);
+
+#endif
diff --git a/src/OutputAPI.hxx b/src/OutputAPI.hxx
new file mode 100644
index 000000000..9c79d9b81
--- /dev/null
+++ b/src/OutputAPI.hxx
@@ -0,0 +1,29 @@
+/*
+ * 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_OUTPUT_API_HXX
+#define MPD_OUTPUT_API_HxX
+
+#include "OutputPlugin.hxx"
+#include "OutputInternal.hxx"
+#include "AudioFormat.hxx"
+#include "Tag.hxx"
+#include "conf.h"
+
+#endif
diff --git a/src/OutputAll.cxx b/src/OutputAll.cxx
new file mode 100644
index 000000000..2e721efa3
--- /dev/null
+++ b/src/OutputAll.cxx
@@ -0,0 +1,595 @@
+/*
+ * 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.
+ */
+
+#include "config.h"
+#include "OutputAll.hxx"
+#include "PlayerControl.hxx"
+#include "OutputInternal.hxx"
+#include "OutputControl.hxx"
+#include "OutputError.hxx"
+#include "MusicBuffer.hxx"
+#include "MusicPipe.hxx"
+#include "MusicChunk.hxx"
+#include "mpd_error.h"
+#include "conf.h"
+#include "notify.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "output"
+
+static AudioFormat input_audio_format;
+
+static struct audio_output **audio_outputs;
+static unsigned int num_audio_outputs;
+
+/**
+ * The #music_buffer object where consumed chunks are returned.
+ */
+static struct music_buffer *g_music_buffer;
+
+/**
+ * The #music_pipe object which feeds all audio outputs. It is filled
+ * by audio_output_all_play().
+ */
+static struct music_pipe *g_mp;
+
+/**
+ * The "elapsed_time" stamp of the most recently finished chunk.
+ */
+static float audio_output_all_elapsed_time = -1.0;
+
+unsigned int audio_output_count(void)
+{
+ return num_audio_outputs;
+}
+
+struct audio_output *
+audio_output_get(unsigned i)
+{
+ assert(i < num_audio_outputs);
+
+ assert(audio_outputs[i] != NULL);
+
+ return audio_outputs[i];
+}
+
+struct audio_output *
+audio_output_find(const char *name)
+{
+ for (unsigned i = 0; i < num_audio_outputs; ++i) {
+ struct audio_output *ao = audio_output_get(i);
+
+ if (strcmp(ao->name, name) == 0)
+ return ao;
+ }
+
+ /* name not found */
+ return NULL;
+}
+
+static unsigned
+audio_output_config_count(void)
+{
+ unsigned int nr = 0;
+ const struct config_param *param = NULL;
+
+ while ((param = config_get_next_param(CONF_AUDIO_OUTPUT, param)))
+ nr++;
+ if (!nr)
+ nr = 1; /* we'll always have at least one device */
+ return nr;
+}
+
+void
+audio_output_all_init(struct player_control *pc)
+{
+ const struct config_param *param = NULL;
+ unsigned int i;
+ GError *error = NULL;
+
+ num_audio_outputs = audio_output_config_count();
+ audio_outputs = g_new(struct audio_output *, num_audio_outputs);
+
+ const config_param empty;
+
+ for (i = 0; i < num_audio_outputs; i++)
+ {
+ unsigned int j;
+
+ param = config_get_next_param(CONF_AUDIO_OUTPUT, param);
+ if (param == nullptr) {
+ /* only allow param to be nullptr if there
+ just one audio output */
+ assert(i == 0);
+ assert(num_audio_outputs == 1);
+
+ param = &empty;
+ }
+
+ struct audio_output *output = audio_output_new(*param, pc, &error);
+ if (output == NULL) {
+ if (param != NULL)
+ MPD_ERROR("line %i: %s",
+ param->line, error->message);
+ else
+ MPD_ERROR("%s", error->message);
+ }
+
+ audio_outputs[i] = output;
+
+ /* require output names to be unique: */
+ for (j = 0; j < i; j++) {
+ if (!strcmp(output->name, audio_outputs[j]->name)) {
+ MPD_ERROR("output devices with identical "
+ "names: %s\n", output->name);
+ }
+ }
+ }
+}
+
+void
+audio_output_all_finish(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < num_audio_outputs; i++) {
+ audio_output_disable(audio_outputs[i]);
+ audio_output_finish(audio_outputs[i]);
+ }
+
+ g_free(audio_outputs);
+ audio_outputs = NULL;
+ num_audio_outputs = 0;
+}
+
+void
+audio_output_all_enable_disable(void)
+{
+ for (unsigned i = 0; i < num_audio_outputs; i++) {
+ struct audio_output *ao = audio_outputs[i];
+ bool enabled;
+
+ ao->mutex.lock();
+ enabled = ao->really_enabled;
+ ao->mutex.unlock();
+
+ if (ao->enabled != enabled) {
+ if (ao->enabled)
+ audio_output_enable(ao);
+ else
+ audio_output_disable(ao);
+ }
+ }
+}
+
+/**
+ * Determine if all (active) outputs have finished the current
+ * command.
+ */
+static bool
+audio_output_all_finished(void)
+{
+ for (unsigned i = 0; i < num_audio_outputs; ++i) {
+ struct audio_output *ao = audio_outputs[i];
+
+ const ScopeLock protect(ao->mutex);
+ if (audio_output_is_open(ao) &&
+ !audio_output_command_is_finished(ao))
+ return false;
+ }
+
+ return true;
+}
+
+static void audio_output_wait_all(void)
+{
+ while (!audio_output_all_finished())
+ audio_output_client_notify.Wait();
+}
+
+/**
+ * Signals all audio outputs which are open.
+ */
+static void
+audio_output_allow_play_all(void)
+{
+ for (unsigned i = 0; i < num_audio_outputs; ++i)
+ audio_output_allow_play(audio_outputs[i]);
+}
+
+static void
+audio_output_reset_reopen(struct audio_output *ao)
+{
+ const ScopeLock protect(ao->mutex);
+
+ if (!ao->open && ao->fail_timer != NULL) {
+ g_timer_destroy(ao->fail_timer);
+ ao->fail_timer = NULL;
+ }
+}
+
+/**
+ * Resets the "reopen" flag on all audio devices. MPD should
+ * immediately retry to open the device instead of waiting for the
+ * timeout when the user wants to start playback.
+ */
+static void
+audio_output_all_reset_reopen(void)
+{
+ for (unsigned i = 0; i < num_audio_outputs; ++i) {
+ struct audio_output *ao = audio_outputs[i];
+
+ audio_output_reset_reopen(ao);
+ }
+}
+
+/**
+ * Opens all output devices which are enabled, but closed.
+ *
+ * @return true if there is at least open output device which is open
+ */
+static bool
+audio_output_all_update(void)
+{
+ unsigned int i;
+ bool ret = false;
+
+ if (!input_audio_format.IsDefined())
+ return false;
+
+ for (i = 0; i < num_audio_outputs; ++i)
+ ret = audio_output_update(audio_outputs[i],
+ input_audio_format, g_mp) || ret;
+
+ return ret;
+}
+
+void
+audio_output_all_set_replay_gain_mode(enum replay_gain_mode mode)
+{
+ for (unsigned i = 0; i < num_audio_outputs; ++i)
+ audio_output_set_replay_gain_mode(audio_outputs[i], mode);
+}
+
+bool
+audio_output_all_play(struct music_chunk *chunk, GError **error_r)
+{
+ bool ret;
+ unsigned int i;
+
+ assert(g_music_buffer != NULL);
+ assert(g_mp != NULL);
+ assert(chunk != NULL);
+ assert(chunk->CheckFormat(input_audio_format));
+
+ ret = audio_output_all_update();
+ if (!ret) {
+ /* TODO: obtain real error */
+ g_set_error(error_r, output_quark(), 0,
+ "Failed to open audio output");
+ return false;
+ }
+
+ music_pipe_push(g_mp, chunk);
+
+ for (i = 0; i < num_audio_outputs; ++i)
+ audio_output_play(audio_outputs[i]);
+
+ return true;
+}
+
+bool
+audio_output_all_open(const AudioFormat audio_format,
+ struct music_buffer *buffer,
+ GError **error_r)
+{
+ bool ret = false, enabled = false;
+ unsigned int i;
+
+ assert(buffer != NULL);
+ assert(g_music_buffer == NULL || g_music_buffer == buffer);
+ assert((g_mp == NULL) == (g_music_buffer == NULL));
+
+ g_music_buffer = buffer;
+
+ /* the audio format must be the same as existing chunks in the
+ pipe */
+ assert(g_mp == NULL || music_pipe_check_format(g_mp, audio_format));
+
+ if (g_mp == NULL)
+ g_mp = music_pipe_new();
+ else
+ /* if the pipe hasn't been cleared, the the audio
+ format must not have changed */
+ assert(music_pipe_empty(g_mp) ||
+ audio_format == input_audio_format);
+
+ input_audio_format = audio_format;
+
+ audio_output_all_reset_reopen();
+ audio_output_all_enable_disable();
+ audio_output_all_update();
+
+ for (i = 0; i < num_audio_outputs; ++i) {
+ if (audio_outputs[i]->enabled)
+ enabled = true;
+
+ if (audio_outputs[i]->open)
+ ret = true;
+ }
+
+ if (!enabled)
+ g_set_error(error_r, output_quark(), 0,
+ "All audio outputs are disabled");
+ else if (!ret)
+ /* TODO: obtain real error */
+ g_set_error(error_r, output_quark(), 0,
+ "Failed to open audio output");
+
+ if (!ret)
+ /* close all devices if there was an error */
+ audio_output_all_close();
+
+ return ret;
+}
+
+/**
+ * Has the specified audio output already consumed this chunk?
+ */
+static bool
+chunk_is_consumed_in(const struct audio_output *ao,
+ const struct music_chunk *chunk)
+{
+ if (!ao->open)
+ return true;
+
+ if (ao->chunk == NULL)
+ return false;
+
+ assert(chunk == ao->chunk || music_pipe_contains(g_mp, ao->chunk));
+
+ if (chunk != ao->chunk) {
+ assert(chunk->next != NULL);
+ return true;
+ }
+
+ return ao->chunk_finished && chunk->next == NULL;
+}
+
+/**
+ * Has this chunk been consumed by all audio outputs?
+ */
+static bool
+chunk_is_consumed(const struct music_chunk *chunk)
+{
+ for (unsigned i = 0; i < num_audio_outputs; ++i) {
+ struct audio_output *ao = audio_outputs[i];
+
+ const ScopeLock protect(ao->mutex);
+ if (!chunk_is_consumed_in(ao, chunk))
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * There's only one chunk left in the pipe (#g_mp), and all audio
+ * outputs have consumed it already. Clear the reference.
+ */
+static void
+clear_tail_chunk(G_GNUC_UNUSED const struct music_chunk *chunk, bool *locked)
+{
+ assert(chunk->next == NULL);
+ assert(music_pipe_contains(g_mp, chunk));
+
+ for (unsigned i = 0; i < num_audio_outputs; ++i) {
+ struct audio_output *ao = audio_outputs[i];
+
+ /* this mutex will be unlocked by the caller when it's
+ ready */
+ ao->mutex.lock();
+ locked[i] = ao->open;
+
+ if (!locked[i]) {
+ ao->mutex.unlock();
+ continue;
+ }
+
+ assert(ao->chunk == chunk);
+ assert(ao->chunk_finished);
+ ao->chunk = NULL;
+ }
+}
+
+unsigned
+audio_output_all_check(void)
+{
+ const struct music_chunk *chunk;
+ bool is_tail;
+ struct music_chunk *shifted;
+ bool locked[num_audio_outputs];
+
+ assert(g_music_buffer != NULL);
+ assert(g_mp != NULL);
+
+ while ((chunk = music_pipe_peek(g_mp)) != NULL) {
+ assert(!music_pipe_empty(g_mp));
+
+ if (!chunk_is_consumed(chunk))
+ /* at least one output is not finished playing
+ this chunk */
+ return music_pipe_size(g_mp);
+
+ if (chunk->length > 0 && chunk->times >= 0.0)
+ /* only update elapsed_time if the chunk
+ provides a defined value */
+ audio_output_all_elapsed_time = chunk->times;
+
+ is_tail = chunk->next == NULL;
+ if (is_tail)
+ /* this is the tail of the pipe - clear the
+ chunk reference in all outputs */
+ clear_tail_chunk(chunk, locked);
+
+ /* remove the chunk from the pipe */
+ shifted = music_pipe_shift(g_mp);
+ assert(shifted == chunk);
+
+ if (is_tail)
+ /* unlock all audio outputs which were locked
+ by clear_tail_chunk() */
+ for (unsigned i = 0; i < num_audio_outputs; ++i)
+ if (locked[i])
+ audio_outputs[i]->mutex.unlock();
+
+ /* return the chunk to the buffer */
+ music_buffer_return(g_music_buffer, shifted);
+ }
+
+ return 0;
+}
+
+bool
+audio_output_all_wait(struct player_control *pc, unsigned threshold)
+{
+ pc->Lock();
+
+ if (audio_output_all_check() < threshold) {
+ pc->Unlock();
+ return true;
+ }
+
+ pc->Wait();
+ pc->Unlock();
+
+ return audio_output_all_check() < threshold;
+}
+
+void
+audio_output_all_pause(void)
+{
+ unsigned int i;
+
+ audio_output_all_update();
+
+ for (i = 0; i < num_audio_outputs; ++i)
+ audio_output_pause(audio_outputs[i]);
+
+ audio_output_wait_all();
+}
+
+void
+audio_output_all_drain(void)
+{
+ for (unsigned i = 0; i < num_audio_outputs; ++i)
+ audio_output_drain_async(audio_outputs[i]);
+
+ audio_output_wait_all();
+}
+
+void
+audio_output_all_cancel(void)
+{
+ unsigned int i;
+
+ /* send the cancel() command to all audio outputs */
+
+ for (i = 0; i < num_audio_outputs; ++i)
+ audio_output_cancel(audio_outputs[i]);
+
+ audio_output_wait_all();
+
+ /* clear the music pipe and return all chunks to the buffer */
+
+ if (g_mp != NULL)
+ music_pipe_clear(g_mp, g_music_buffer);
+
+ /* the audio outputs are now waiting for a signal, to
+ synchronize the cleared music pipe */
+
+ audio_output_allow_play_all();
+
+ /* invalidate elapsed_time */
+
+ audio_output_all_elapsed_time = -1.0;
+}
+
+void
+audio_output_all_close(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < num_audio_outputs; ++i)
+ audio_output_close(audio_outputs[i]);
+
+ if (g_mp != NULL) {
+ assert(g_music_buffer != NULL);
+
+ music_pipe_clear(g_mp, g_music_buffer);
+ music_pipe_free(g_mp);
+ g_mp = NULL;
+ }
+
+ g_music_buffer = NULL;
+
+ input_audio_format.Clear();
+
+ audio_output_all_elapsed_time = -1.0;
+}
+
+void
+audio_output_all_release(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < num_audio_outputs; ++i)
+ audio_output_release(audio_outputs[i]);
+
+ if (g_mp != NULL) {
+ assert(g_music_buffer != NULL);
+
+ music_pipe_clear(g_mp, g_music_buffer);
+ music_pipe_free(g_mp);
+ g_mp = NULL;
+ }
+
+ g_music_buffer = NULL;
+
+ input_audio_format.Clear();
+
+ audio_output_all_elapsed_time = -1.0;
+}
+
+void
+audio_output_all_song_border(void)
+{
+ /* clear the elapsed_time pointer at the beginning of a new
+ song */
+ audio_output_all_elapsed_time = 0.0;
+}
+
+float
+audio_output_all_get_elapsed_time(void)
+{
+ return audio_output_all_elapsed_time;
+}
diff --git a/src/OutputAll.hxx b/src/OutputAll.hxx
new file mode 100644
index 000000000..10f8196aa
--- /dev/null
+++ b/src/OutputAll.hxx
@@ -0,0 +1,169 @@
+/*
+ * 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.
+ */
+
+/*
+ * Functions for dealing with all configured (enabled) audion outputs
+ * at once.
+ *
+ */
+
+#ifndef OUTPUT_ALL_H
+#define OUTPUT_ALL_H
+
+#include "replay_gain_info.h"
+#include "gerror.h"
+
+struct AudioFormat;
+struct music_buffer;
+struct music_chunk;
+struct player_control;
+
+/**
+ * Global initialization: load audio outputs from the configuration
+ * file and initialize them.
+ */
+void
+audio_output_all_init(struct player_control *pc);
+
+/**
+ * Global finalization: free memory occupied by audio outputs. All
+ */
+void
+audio_output_all_finish(void);
+
+/**
+ * Returns the total number of audio output devices, including those
+ * who are disabled right now.
+ */
+unsigned int audio_output_count(void);
+
+/**
+ * Returns the "i"th audio output device.
+ */
+struct audio_output *
+audio_output_get(unsigned i);
+
+/**
+ * Returns the audio output device with the specified name. Returns
+ * NULL if the name does not exist.
+ */
+struct audio_output *
+audio_output_find(const char *name);
+
+/**
+ * Checks the "enabled" flag of all audio outputs, and if one has
+ * changed, commit the change.
+ */
+void
+audio_output_all_enable_disable(void);
+
+/**
+ * Opens all audio outputs which are not disabled.
+ *
+ * @param audio_format the preferred audio format
+ * @param buffer the #music_buffer where consumed #music_chunk objects
+ * should be returned
+ * @return true on success, false on failure
+ */
+bool
+audio_output_all_open(AudioFormat audio_format,
+ struct music_buffer *buffer,
+ GError **error_r);
+
+/**
+ * Closes all audio outputs.
+ */
+void
+audio_output_all_close(void);
+
+/**
+ * Closes all audio outputs. Outputs with the "always_on" flag are
+ * put into pause mode.
+ */
+void
+audio_output_all_release(void);
+
+void
+audio_output_all_set_replay_gain_mode(enum replay_gain_mode mode);
+
+/**
+ * Enqueue a #music_chunk object for playing, i.e. pushes it to a
+ * #music_pipe.
+ *
+ * @param chunk the #music_chunk object to be played
+ * @return true on success, false if no audio output was able to play
+ * (all closed then)
+ */
+bool
+audio_output_all_play(struct music_chunk *chunk, GError **error_r);
+
+/**
+ * Checks if the output devices have drained their music pipe, and
+ * returns the consumed music chunks to the #music_buffer.
+ *
+ * @return the number of chunks to play left in the #music_pipe
+ */
+unsigned
+audio_output_all_check(void);
+
+/**
+ * Checks if the size of the #music_pipe is below the #threshold. If
+ * not, it attempts to synchronize with all output threads, and waits
+ * until another #music_chunk is finished.
+ *
+ * @param threshold the maximum number of chunks in the pipe
+ * @return true if there are less than #threshold chunks in the pipe
+ */
+bool
+audio_output_all_wait(struct player_control *pc, unsigned threshold);
+
+/**
+ * Puts all audio outputs into pause mode. Most implementations will
+ * simply close it then.
+ */
+void
+audio_output_all_pause(void);
+
+/**
+ * Drain all audio outputs.
+ */
+void
+audio_output_all_drain(void);
+
+/**
+ * Try to cancel data which may still be in the device's buffers.
+ */
+void
+audio_output_all_cancel(void);
+
+/**
+ * Indicate that a new song will begin now.
+ */
+void
+audio_output_all_song_border(void);
+
+/**
+ * Returns the "elapsed_time" stamp of the most recently finished
+ * chunk. A negative value is returned when no chunk has been
+ * finished yet.
+ */
+float
+audio_output_all_get_elapsed_time(void);
+
+#endif
diff --git a/src/OutputCommand.cxx b/src/OutputCommand.cxx
new file mode 100644
index 000000000..bf051babf
--- /dev/null
+++ b/src/OutputCommand.cxx
@@ -0,0 +1,86 @@
+/*
+ * 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.
+ */
+
+/*
+ * Glue functions for controlling the audio outputs over the MPD
+ * protocol. These functions perform extra validation on all
+ * parameters, because they might be from an untrusted source.
+ *
+ */
+
+#include "config.h"
+#include "OutputCommand.hxx"
+#include "OutputAll.hxx"
+#include "OutputInternal.hxx"
+#include "OutputPlugin.hxx"
+#include "PlayerControl.hxx"
+#include "MixerControl.hxx"
+#include "Idle.hxx"
+
+extern unsigned audio_output_state_version;
+
+bool
+audio_output_enable_index(unsigned idx)
+{
+ struct audio_output *ao;
+
+ if (idx >= audio_output_count())
+ return false;
+
+ ao = audio_output_get(idx);
+ if (ao->enabled)
+ return true;
+
+ ao->enabled = true;
+ idle_add(IDLE_OUTPUT);
+
+ ao->player_control->UpdateAudio();
+
+ ++audio_output_state_version;
+
+ return true;
+}
+
+bool
+audio_output_disable_index(unsigned idx)
+{
+ struct audio_output *ao;
+
+ if (idx >= audio_output_count())
+ return false;
+
+ ao = audio_output_get(idx);
+ if (!ao->enabled)
+ return true;
+
+ ao->enabled = false;
+ idle_add(IDLE_OUTPUT);
+
+ Mixer *mixer = ao->mixer;
+ if (mixer != NULL) {
+ mixer_close(mixer);
+ idle_add(IDLE_MIXER);
+ }
+
+ ao->player_control->UpdateAudio();
+
+ ++audio_output_state_version;
+
+ return true;
+}
diff --git a/src/OutputCommand.hxx b/src/OutputCommand.hxx
new file mode 100644
index 000000000..74eaf8f1c
--- /dev/null
+++ b/src/OutputCommand.hxx
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+/*
+ * Glue functions for controlling the audio outputs over the MPD
+ * protocol. These functions perform extra validation on all
+ * parameters, because they might be from an untrusted source.
+ *
+ */
+
+#ifndef MPD_OUTPUT_COMMAND_HXX
+#define MPD_OUTPUT_COMMAND_HXX
+
+/**
+ * Enables an audio output. Returns false if the specified output
+ * does not exist.
+ */
+bool
+audio_output_enable_index(unsigned idx);
+
+/**
+ * Disables an audio output. Returns false if the specified output
+ * does not exist.
+ */
+bool
+audio_output_disable_index(unsigned idx);
+
+#endif
diff --git a/src/OutputCommands.cxx b/src/OutputCommands.cxx
new file mode 100644
index 000000000..7d626477a
--- /dev/null
+++ b/src/OutputCommands.cxx
@@ -0,0 +1,74 @@
+/*
+ * 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 "OutputCommands.hxx"
+#include "OutputPrint.hxx"
+#include "OutputCommand.hxx"
+#include "protocol/Result.hxx"
+#include "protocol/ArgParser.hxx"
+
+#include <string.h>
+
+enum command_return
+handle_enableoutput(Client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ unsigned device;
+ bool ret;
+
+ if (!check_unsigned(client, &device, argv[1]))
+ return COMMAND_RETURN_ERROR;
+
+ ret = audio_output_enable_index(device);
+ if (!ret) {
+ command_error(client, ACK_ERROR_NO_EXIST,
+ "No such audio output");
+ return COMMAND_RETURN_ERROR;
+ }
+
+ return COMMAND_RETURN_OK;
+}
+
+enum command_return
+handle_disableoutput(Client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ unsigned device;
+ bool ret;
+
+ if (!check_unsigned(client, &device, argv[1]))
+ return COMMAND_RETURN_ERROR;
+
+ ret = audio_output_disable_index(device);
+ if (!ret) {
+ command_error(client, ACK_ERROR_NO_EXIST,
+ "No such audio output");
+ return COMMAND_RETURN_ERROR;
+ }
+
+ return COMMAND_RETURN_OK;
+}
+
+enum command_return
+handle_devices(Client *client,
+ G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
+{
+ printAudioDevices(client);
+
+ return COMMAND_RETURN_OK;
+}
diff --git a/src/OutputCommands.hxx b/src/OutputCommands.hxx
new file mode 100644
index 000000000..4f7082bfb
--- /dev/null
+++ b/src/OutputCommands.hxx
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+#ifndef MPD_OUTPUT_COMMANDS_HXX
+#define MPD_OUTPUT_COMMANDS_HXX
+
+#include "command.h"
+
+class Client;
+
+enum command_return
+handle_enableoutput(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_disableoutput(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_devices(Client *client, int argc, char *argv[]);
+
+#endif
diff --git a/src/OutputControl.cxx b/src/OutputControl.cxx
new file mode 100644
index 000000000..b8f3a3ea4
--- /dev/null
+++ b/src/OutputControl.cxx
@@ -0,0 +1,333 @@
+/*
+ * 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 "OutputControl.hxx"
+#include "OutputThread.hxx"
+#include "OutputInternal.hxx"
+#include "OutputPlugin.hxx"
+#include "MixerPlugin.hxx"
+#include "MixerControl.hxx"
+#include "notify.hxx"
+#include "filter/ReplayGainFilterPlugin.hxx"
+#include "FilterPlugin.hxx"
+
+#include <assert.h>
+#include <stdlib.h>
+
+enum {
+ /** after a failure, wait this number of seconds before
+ automatically reopening the device */
+ REOPEN_AFTER = 10,
+};
+
+struct notify audio_output_client_notify;
+
+/**
+ * Waits for command completion.
+ *
+ * @param ao the #audio_output instance; must be locked
+ */
+static void ao_command_wait(struct audio_output *ao)
+{
+ while (ao->command != AO_COMMAND_NONE) {
+ ao->mutex.unlock();
+ audio_output_client_notify.Wait();
+ ao->mutex.lock();
+ }
+}
+
+/**
+ * Sends a command to the #audio_output object, but does not wait for
+ * completion.
+ *
+ * @param ao the #audio_output instance; must be locked
+ */
+static void ao_command_async(struct audio_output *ao,
+ enum audio_output_command cmd)
+{
+ assert(ao->command == AO_COMMAND_NONE);
+ ao->command = cmd;
+ ao->cond.signal();
+}
+
+/**
+ * Sends a command to the #audio_output object and waits for
+ * completion.
+ *
+ * @param ao the #audio_output instance; must be locked
+ */
+static void
+ao_command(struct audio_output *ao, enum audio_output_command cmd)
+{
+ ao_command_async(ao, cmd);
+ ao_command_wait(ao);
+}
+
+/**
+ * Lock the #audio_output object and execute the command
+ * synchronously.
+ */
+static void
+ao_lock_command(struct audio_output *ao, enum audio_output_command cmd)
+{
+ const ScopeLock protect(ao->mutex);
+ ao_command(ao, cmd);
+}
+
+void
+audio_output_set_replay_gain_mode(struct audio_output *ao,
+ enum replay_gain_mode mode)
+{
+ if (ao->replay_gain_filter != NULL)
+ replay_gain_filter_set_mode(ao->replay_gain_filter, mode);
+}
+
+void
+audio_output_enable(struct audio_output *ao)
+{
+ if (ao->thread == NULL) {
+ if (ao->plugin->enable == NULL) {
+ /* don't bother to start the thread now if the
+ device doesn't even have a enable() method;
+ just assign the variable and we're done */
+ ao->really_enabled = true;
+ return;
+ }
+
+ audio_output_thread_start(ao);
+ }
+
+ ao_lock_command(ao, AO_COMMAND_ENABLE);
+}
+
+void
+audio_output_disable(struct audio_output *ao)
+{
+ if (ao->thread == NULL) {
+ if (ao->plugin->disable == NULL)
+ ao->really_enabled = false;
+ else
+ /* if there's no thread yet, the device cannot
+ be enabled */
+ assert(!ao->really_enabled);
+
+ return;
+ }
+
+ ao_lock_command(ao, AO_COMMAND_DISABLE);
+}
+
+/**
+ * Object must be locked (and unlocked) by the caller.
+ */
+static bool
+audio_output_open(struct audio_output *ao,
+ const AudioFormat audio_format,
+ const struct music_pipe *mp)
+{
+ bool open;
+
+ assert(ao != NULL);
+ assert(ao->allow_play);
+ assert(audio_format.IsValid());
+ assert(mp != NULL);
+
+ if (ao->fail_timer != NULL) {
+ g_timer_destroy(ao->fail_timer);
+ ao->fail_timer = NULL;
+ }
+
+ if (ao->open && audio_format == ao->in_audio_format) {
+ assert(ao->pipe == mp ||
+ (ao->always_on && ao->pause));
+
+ if (ao->pause) {
+ ao->chunk = NULL;
+ ao->pipe = mp;
+
+ /* unpause with the CANCEL command; this is a
+ hack, but suits well for forcing the thread
+ to leave the ao_pause() thread, and we need
+ to flush the device buffer anyway */
+
+ /* we're not using audio_output_cancel() here,
+ because that function is asynchronous */
+ ao_command(ao, AO_COMMAND_CANCEL);
+ }
+
+ return true;
+ }
+
+ ao->in_audio_format = audio_format;
+ ao->chunk = NULL;
+
+ ao->pipe = mp;
+
+ if (ao->thread == NULL)
+ audio_output_thread_start(ao);
+
+ ao_command(ao, ao->open ? AO_COMMAND_REOPEN : AO_COMMAND_OPEN);
+ open = ao->open;
+
+ if (open && ao->mixer != NULL) {
+ GError *error = NULL;
+
+ if (!mixer_open(ao->mixer, &error)) {
+ g_warning("Failed to open mixer for '%s': %s",
+ ao->name, error->message);
+ g_error_free(error);
+ }
+ }
+
+ return open;
+}
+
+/**
+ * Same as audio_output_close(), but expects the lock to be held by
+ * the caller.
+ */
+static void
+audio_output_close_locked(struct audio_output *ao)
+{
+ assert(ao != NULL);
+ assert(ao->allow_play);
+
+ if (ao->mixer != NULL)
+ mixer_auto_close(ao->mixer);
+
+ assert(!ao->open || ao->fail_timer == NULL);
+
+ if (ao->open)
+ ao_command(ao, AO_COMMAND_CLOSE);
+ else if (ao->fail_timer != NULL) {
+ g_timer_destroy(ao->fail_timer);
+ ao->fail_timer = NULL;
+ }
+}
+
+bool
+audio_output_update(struct audio_output *ao,
+ const AudioFormat audio_format,
+ const struct music_pipe *mp)
+{
+ assert(mp != NULL);
+
+ const ScopeLock protect(ao->mutex);
+
+ if (ao->enabled && ao->really_enabled) {
+ if (ao->fail_timer == NULL ||
+ g_timer_elapsed(ao->fail_timer, NULL) > REOPEN_AFTER) {
+ return audio_output_open(ao, audio_format, mp);
+ }
+ } else if (audio_output_is_open(ao))
+ audio_output_close_locked(ao);
+
+ return false;
+}
+
+void
+audio_output_play(struct audio_output *ao)
+{
+ const ScopeLock protect(ao->mutex);
+
+ assert(ao->allow_play);
+
+ if (audio_output_is_open(ao))
+ ao->cond.signal();
+}
+
+void audio_output_pause(struct audio_output *ao)
+{
+ if (ao->mixer != NULL && ao->plugin->pause == NULL)
+ /* the device has no pause mode: close the mixer,
+ unless its "global" flag is set (checked by
+ mixer_auto_close()) */
+ mixer_auto_close(ao->mixer);
+
+ const ScopeLock protect(ao->mutex);
+
+ assert(ao->allow_play);
+ if (audio_output_is_open(ao))
+ ao_command_async(ao, AO_COMMAND_PAUSE);
+}
+
+void
+audio_output_drain_async(struct audio_output *ao)
+{
+ const ScopeLock protect(ao->mutex);
+
+ assert(ao->allow_play);
+ if (audio_output_is_open(ao))
+ ao_command_async(ao, AO_COMMAND_DRAIN);
+}
+
+void audio_output_cancel(struct audio_output *ao)
+{
+ const ScopeLock protect(ao->mutex);
+
+ if (audio_output_is_open(ao)) {
+ ao->allow_play = false;
+ ao_command_async(ao, AO_COMMAND_CANCEL);
+ }
+}
+
+void
+audio_output_allow_play(struct audio_output *ao)
+{
+ const ScopeLock protect(ao->mutex);
+
+ ao->allow_play = true;
+ if (audio_output_is_open(ao))
+ ao->cond.signal();
+}
+
+void
+audio_output_release(struct audio_output *ao)
+{
+ if (ao->always_on)
+ audio_output_pause(ao);
+ else
+ audio_output_close(ao);
+}
+
+void audio_output_close(struct audio_output *ao)
+{
+ assert(ao != NULL);
+ assert(!ao->open || ao->fail_timer == NULL);
+
+ const ScopeLock protect(ao->mutex);
+ audio_output_close_locked(ao);
+}
+
+void audio_output_finish(struct audio_output *ao)
+{
+ audio_output_close(ao);
+
+ assert(ao->fail_timer == NULL);
+
+ if (ao->thread != NULL) {
+ assert(ao->allow_play);
+ ao_lock_command(ao, AO_COMMAND_KILL);
+ g_thread_join(ao->thread);
+ ao->thread = NULL;
+ }
+
+ audio_output_free(ao);
+}
diff --git a/src/OutputControl.hxx b/src/OutputControl.hxx
new file mode 100644
index 000000000..373b1849c
--- /dev/null
+++ b/src/OutputControl.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_OUTPUT_CONTROL_HXX
+#define MPD_OUTPUT_CONTROL_HXX
+
+#include "replay_gain_info.h"
+
+#include <stddef.h>
+
+struct audio_output;
+struct AudioFormat;
+struct config_param;
+struct music_pipe;
+struct player_control;
+
+void
+audio_output_set_replay_gain_mode(struct audio_output *ao,
+ enum replay_gain_mode mode);
+
+/**
+ * Enables the device.
+ */
+void
+audio_output_enable(struct audio_output *ao);
+
+/**
+ * Disables the device.
+ */
+void
+audio_output_disable(struct audio_output *ao);
+
+/**
+ * Opens or closes the device, depending on the "enabled" flag.
+ *
+ * @return true if the device is open
+ */
+bool
+audio_output_update(struct audio_output *ao,
+ AudioFormat audio_format,
+ const struct music_pipe *mp);
+
+void
+audio_output_play(struct audio_output *ao);
+
+void audio_output_pause(struct audio_output *ao);
+
+void
+audio_output_drain_async(struct 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);
+
+/**
+ * Set the "allow_play" and signal the thread.
+ */
+void
+audio_output_allow_play(struct audio_output *ao);
+
+void audio_output_close(struct 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);
+
+void audio_output_finish(struct audio_output *ao);
+
+#endif
diff --git a/src/OutputError.hxx b/src/OutputError.hxx
new file mode 100644
index 000000000..451df9857
--- /dev/null
+++ b/src/OutputError.hxx
@@ -0,0 +1,35 @@
+/*
+ * 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_OUTPUT_ERROR_HXX
+#define MPD_OUTPUT_ERROR_HXX
+
+#include <glib.h>
+
+/**
+ * Quark for GError.domain.
+ */
+G_GNUC_CONST
+static inline GQuark
+output_quark(void)
+{
+ return g_quark_from_static_string("output");
+}
+
+#endif
diff --git a/src/OutputFinish.cxx b/src/OutputFinish.cxx
new file mode 100644
index 000000000..2346161aa
--- /dev/null
+++ b/src/OutputFinish.cxx
@@ -0,0 +1,51 @@
+/*
+ * 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 "OutputInternal.hxx"
+#include "OutputPlugin.hxx"
+#include "MixerControl.hxx"
+#include "FilterInternal.hxx"
+
+#include <assert.h>
+
+void
+ao_base_finish(struct audio_output *ao)
+{
+ assert(!ao->open);
+ assert(ao->fail_timer == NULL);
+ assert(ao->thread == NULL);
+
+ if (ao->mixer != NULL)
+ mixer_free(ao->mixer);
+
+ delete ao->replay_gain_filter;
+ delete ao->other_replay_gain_filter;
+ delete ao->filter;
+}
+
+void
+audio_output_free(struct audio_output *ao)
+{
+ assert(!ao->open);
+ assert(ao->fail_timer == NULL);
+ assert(ao->thread == NULL);
+
+ ao_plugin_finish(ao);
+}
diff --git a/src/OutputInit.cxx b/src/OutputInit.cxx
new file mode 100644
index 000000000..2e50515c8
--- /dev/null
+++ b/src/OutputInit.cxx
@@ -0,0 +1,331 @@
+/*
+ * 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 "OutputInternal.hxx"
+#include "OutputControl.hxx"
+#include "OutputList.hxx"
+#include "OutputError.hxx"
+#include "OutputAPI.hxx"
+#include "FilterConfig.hxx"
+#include "AudioParser.hxx"
+#include "MixerList.hxx"
+#include "MixerType.hxx"
+#include "MixerControl.hxx"
+#include "mixer/SoftwareMixerPlugin.hxx"
+#include "FilterPlugin.hxx"
+#include "FilterRegistry.hxx"
+#include "filter/AutoConvertFilterPlugin.hxx"
+#include "filter/ReplayGainFilterPlugin.hxx"
+#include "filter/ChainFilterPlugin.hxx"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "output"
+
+#define AUDIO_OUTPUT_TYPE "type"
+#define AUDIO_OUTPUT_NAME "name"
+#define AUDIO_OUTPUT_FORMAT "format"
+#define AUDIO_FILTERS "filters"
+
+static const struct audio_output_plugin *
+audio_output_detect(GError **error)
+{
+ g_warning("Attempt to detect audio output device");
+
+ audio_output_plugins_for_each(plugin) {
+ if (plugin->test_default_device == NULL)
+ continue;
+
+ g_warning("Attempting to detect a %s audio device",
+ plugin->name);
+ if (ao_plugin_test_default_device(plugin))
+ return plugin;
+ }
+
+ g_set_error(error, output_quark(), 0,
+ "Unable to detect an audio device");
+ return NULL;
+}
+
+/**
+ * Determines the mixer type which should be used for the specified
+ * configuration block.
+ *
+ * This handles the deprecated options mixer_type (global) and
+ * mixer_enabled, if the mixer_type setting is not configured.
+ */
+gcc_pure
+static enum mixer_type
+audio_output_mixer_type(const config_param &param)
+{
+ /* read the local "mixer_type" setting */
+ const char *p = param.GetBlockValue("mixer_type");
+ if (p != NULL)
+ return mixer_type_parse(p);
+
+ /* try the local "mixer_enabled" setting next (deprecated) */
+ if (!param.GetBlockValue("mixer_enabled", true))
+ return MIXER_TYPE_NONE;
+
+ /* fall back to the global "mixer_type" setting (also
+ deprecated) */
+ return mixer_type_parse(config_get_string(CONF_MIXER_TYPE,
+ "hardware"));
+}
+
+static Mixer *
+audio_output_load_mixer(struct audio_output *ao,
+ const config_param &param,
+ const struct mixer_plugin *plugin,
+ Filter &filter_chain,
+ GError **error_r)
+{
+ Mixer *mixer;
+
+ switch (audio_output_mixer_type(param)) {
+ case MIXER_TYPE_NONE:
+ case MIXER_TYPE_UNKNOWN:
+ return NULL;
+
+ case MIXER_TYPE_HARDWARE:
+ if (plugin == NULL)
+ return NULL;
+
+ return mixer_new(plugin, ao, param, error_r);
+
+ case MIXER_TYPE_SOFTWARE:
+ mixer = mixer_new(&software_mixer_plugin, nullptr,
+ config_param(),
+ nullptr);
+ assert(mixer != NULL);
+
+ filter_chain_append(filter_chain, "software_mixer",
+ software_mixer_get_filter(mixer));
+ return mixer;
+ }
+
+ assert(false);
+ gcc_unreachable();
+}
+
+bool
+ao_base_init(struct audio_output *ao,
+ const struct audio_output_plugin *plugin,
+ const config_param &param, GError **error_r)
+{
+ assert(ao != NULL);
+ assert(plugin != NULL);
+ assert(plugin->finish != NULL);
+ assert(plugin->open != NULL);
+ assert(plugin->close != NULL);
+ assert(plugin->play != NULL);
+
+ GError *error = NULL;
+
+ if (!param.IsNull()) {
+ ao->name = param.GetBlockValue(AUDIO_OUTPUT_NAME);
+ if (ao->name == NULL) {
+ g_set_error(error_r, output_quark(), 0,
+ "Missing \"name\" configuration");
+ return false;
+ }
+
+ const char *p = param.GetBlockValue(AUDIO_OUTPUT_FORMAT);
+ if (p != NULL) {
+ bool success =
+ audio_format_parse(ao->config_audio_format,
+ p, true, error_r);
+ if (!success)
+ return false;
+ } else
+ ao->config_audio_format.Clear();
+ } else {
+ ao->name = "default detected output";
+
+ ao->config_audio_format.Clear();
+ }
+
+ ao->plugin = plugin;
+ ao->tags = param.GetBlockValue("tags", true);
+ ao->always_on = param.GetBlockValue("always_on", false);
+ ao->enabled = param.GetBlockValue("enabled", true);
+ ao->really_enabled = false;
+ ao->open = false;
+ ao->pause = false;
+ ao->allow_play = true;
+ ao->fail_timer = NULL;
+
+ /* set up the filter chain */
+
+ ao->filter = filter_chain_new();
+ assert(ao->filter != NULL);
+
+ /* create the normalization filter (if configured) */
+
+ if (config_get_bool(CONF_VOLUME_NORMALIZATION, false)) {
+ Filter *normalize_filter =
+ filter_new(&normalize_filter_plugin, config_param(),
+ nullptr);
+ assert(normalize_filter != NULL);
+
+ filter_chain_append(*ao->filter, "normalize",
+ autoconvert_filter_new(normalize_filter));
+ }
+
+ filter_chain_parse(*ao->filter,
+ param.GetBlockValue(AUDIO_FILTERS, ""),
+ &error
+ );
+
+ // It's not really fatal - Part of the filter chain has been set up already
+ // and even an empty one will work (if only with unexpected behaviour)
+ if (error != NULL) {
+ g_warning("Failed to initialize filter chain for '%s': %s",
+ ao->name, error->message);
+ g_error_free(error);
+ }
+
+ ao->thread = NULL;
+ ao->command = AO_COMMAND_NONE;
+
+ ao->mixer = NULL;
+ ao->replay_gain_filter = NULL;
+ ao->other_replay_gain_filter = NULL;
+
+ /* done */
+
+ return true;
+}
+
+static bool
+audio_output_setup(struct audio_output *ao, const config_param &param,
+ GError **error_r)
+{
+
+ /* create the replay_gain filter */
+
+ const char *replay_gain_handler =
+ param.GetBlockValue("replay_gain_handler", "software");
+
+ if (strcmp(replay_gain_handler, "none") != 0) {
+ ao->replay_gain_filter = filter_new(&replay_gain_filter_plugin,
+ param, NULL);
+ assert(ao->replay_gain_filter != NULL);
+
+ ao->replay_gain_serial = 0;
+
+ ao->other_replay_gain_filter = filter_new(&replay_gain_filter_plugin,
+ param, NULL);
+ assert(ao->other_replay_gain_filter != NULL);
+
+ ao->other_replay_gain_serial = 0;
+ } else {
+ ao->replay_gain_filter = NULL;
+ ao->other_replay_gain_filter = NULL;
+ }
+
+ /* set up the mixer */
+
+ GError *error = NULL;
+ ao->mixer = audio_output_load_mixer(ao, param,
+ ao->plugin->mixer_plugin,
+ *ao->filter, &error);
+ if (ao->mixer == NULL && error != NULL) {
+ g_warning("Failed to initialize hardware mixer for '%s': %s",
+ ao->name, error->message);
+ g_error_free(error);
+ }
+
+ /* use the hardware mixer for replay gain? */
+
+ if (strcmp(replay_gain_handler, "mixer") == 0) {
+ if (ao->mixer != NULL)
+ replay_gain_filter_set_mixer(ao->replay_gain_filter,
+ ao->mixer, 100);
+ else
+ g_warning("No such mixer for output '%s'", ao->name);
+ } else if (strcmp(replay_gain_handler, "software") != 0 &&
+ ao->replay_gain_filter != NULL) {
+ g_set_error(error_r, output_quark(), 0,
+ "Invalid \"replay_gain_handler\" value");
+ return false;
+ }
+
+ /* the "convert" filter must be the last one in the chain */
+
+ ao->convert_filter = filter_new(&convert_filter_plugin, config_param(),
+ nullptr);
+ assert(ao->convert_filter != NULL);
+
+ filter_chain_append(*ao->filter, "convert", ao->convert_filter);
+
+ return true;
+}
+
+struct audio_output *
+audio_output_new(const config_param &param,
+ struct player_control *pc,
+ GError **error_r)
+{
+ const struct audio_output_plugin *plugin;
+
+ if (!param.IsNull()) {
+ const char *p;
+
+ p = param.GetBlockValue(AUDIO_OUTPUT_TYPE);
+ if (p == NULL) {
+ g_set_error(error_r, output_quark(), 0,
+ "Missing \"type\" configuration");
+ return nullptr;
+ }
+
+ plugin = audio_output_plugin_get(p);
+ if (plugin == NULL) {
+ g_set_error(error_r, output_quark(), 0,
+ "No such audio output plugin: %s", p);
+ return nullptr;
+ }
+ } else {
+ g_warning("No 'audio_output' defined in config file\n");
+
+ plugin = audio_output_detect(error_r);
+ if (plugin == NULL)
+ return nullptr;
+
+ g_message("Successfully detected a %s audio device",
+ plugin->name);
+ }
+
+ struct audio_output *ao = ao_plugin_init(plugin, param, error_r);
+ if (ao == NULL)
+ return NULL;
+
+ if (!audio_output_setup(ao, param, error_r)) {
+ ao_plugin_finish(ao);
+ return NULL;
+ }
+
+ ao->player_control = pc;
+ return ao;
+}
diff --git a/src/OutputInternal.hxx b/src/OutputInternal.hxx
new file mode 100644
index 000000000..20e48279f
--- /dev/null
+++ b/src/OutputInternal.hxx
@@ -0,0 +1,279 @@
+/*
+ * 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_OUTPUT_INTERNAL_HXX
+#define MPD_OUTPUT_INTERNAL_HXX
+
+#include "AudioFormat.hxx"
+#include "pcm/PcmBuffer.hxx"
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+
+#include <glib.h>
+
+#include <time.h>
+
+class Filter;
+struct config_param;
+
+enum audio_output_command {
+ AO_COMMAND_NONE = 0,
+ AO_COMMAND_ENABLE,
+ AO_COMMAND_DISABLE,
+ AO_COMMAND_OPEN,
+
+ /**
+ * This command is invoked when the input audio format
+ * changes.
+ */
+ AO_COMMAND_REOPEN,
+
+ AO_COMMAND_CLOSE,
+ AO_COMMAND_PAUSE,
+
+ /**
+ * Drains the internal (hardware) buffers of the device. This
+ * operation may take a while to complete.
+ */
+ AO_COMMAND_DRAIN,
+
+ AO_COMMAND_CANCEL,
+ AO_COMMAND_KILL
+};
+
+struct audio_output {
+ /**
+ * The device's configured display name.
+ */
+ const char *name;
+
+ /**
+ * The plugin which implements this output device.
+ */
+ const struct audio_output_plugin *plugin;
+
+ /**
+ * The #mixer object associated with this audio output device.
+ * May be NULL if none is available, or if software volume is
+ * configured.
+ */
+ class Mixer *mixer;
+
+ /**
+ * Will this output receive tags from the decoder? The
+ * default is true, but it may be configured to false to
+ * suppress sending tags to the output.
+ */
+ bool tags;
+
+ /**
+ * Shall this output always play something (i.e. silence),
+ * even when playback is stopped?
+ */
+ bool always_on;
+
+ /**
+ * Has the user enabled this device?
+ */
+ bool enabled;
+
+ /**
+ * Is this device actually enabled, i.e. the "enable" method
+ * has succeeded?
+ */
+ bool really_enabled;
+
+ /**
+ * Is the device (already) open and functional?
+ *
+ * This attribute may only be modified by the output thread.
+ * It is protected with #mutex: write accesses inside the
+ * output thread and read accesses outside of it may only be
+ * performed while the lock is held.
+ */
+ bool open;
+
+ /**
+ * Is the device paused? i.e. the output thread is in the
+ * ao_pause() loop.
+ */
+ bool pause;
+
+ /**
+ * When this flag is set, the output thread will not do any
+ * playback. It will wait until the flag is cleared.
+ *
+ * This is used to synchronize the "clear" operation on the
+ * shared music pipe during the CANCEL command.
+ */
+ bool allow_play;
+
+ /**
+ * If not NULL, the device has failed, and this timer is used
+ * to estimate how long it should stay disabled (unless
+ * explicitly reopened with "play").
+ */
+ GTimer *fail_timer;
+
+ /**
+ * The configured audio format.
+ */
+ AudioFormat config_audio_format;
+
+ /**
+ * The audio_format in which audio data is received from the
+ * player thread (which in turn receives it from the decoder).
+ */
+ AudioFormat in_audio_format;
+
+ /**
+ * The audio_format which is really sent to the device. This
+ * is basically config_audio_format (if configured) or
+ * in_audio_format, but may have been modified by
+ * plugin->open().
+ */
+ AudioFormat out_audio_format;
+
+ /**
+ * The buffer used to allocate the cross-fading result.
+ */
+ PcmBuffer cross_fade_buffer;
+
+ /**
+ * The filter object of this audio output. This is an
+ * instance of chain_filter_plugin.
+ */
+ Filter *filter;
+
+ /**
+ * The replay_gain_filter_plugin instance of this audio
+ * output.
+ */
+ Filter *replay_gain_filter;
+
+ /**
+ * The serial number of the last replay gain info. 0 means no
+ * replay gain info was available.
+ */
+ unsigned replay_gain_serial;
+
+ /**
+ * The replay_gain_filter_plugin instance of this audio
+ * output, to be applied to the second chunk during
+ * cross-fading.
+ */
+ Filter *other_replay_gain_filter;
+
+ /**
+ * The serial number of the last replay gain info by the
+ * "other" chunk during cross-fading.
+ */
+ unsigned other_replay_gain_serial;
+
+ /**
+ * The convert_filter_plugin instance of this audio output.
+ * It is the last item in the filter chain, and is responsible
+ * for converting the input data into the appropriate format
+ * for this audio output.
+ */
+ Filter *convert_filter;
+
+ /**
+ * The thread handle, or NULL if the output thread isn't
+ * running.
+ */
+ GThread *thread;
+
+ /**
+ * The next command to be performed by the output thread.
+ */
+ enum audio_output_command command;
+
+ /**
+ * The music pipe which provides music chunks to be played.
+ */
+ const struct music_pipe *pipe;
+
+ /**
+ * This mutex protects #open, #fail_timer, #chunk and
+ * #chunk_finished.
+ */
+ Mutex mutex;
+
+ /**
+ * This condition object wakes up the output thread after
+ * #command has been set.
+ */
+ Cond cond;
+
+ /**
+ * The player_control object which "owns" this output. This
+ * object is needed to signal command completion.
+ */
+ struct player_control *player_control;
+
+ /**
+ * The #music_chunk which is currently being played. All
+ * chunks before this one may be returned to the
+ * #music_buffer, because they are not going to be used by
+ * this output anymore.
+ */
+ const struct music_chunk *chunk;
+
+ /**
+ * Has the output finished playing #chunk?
+ */
+ bool chunk_finished;
+};
+
+/**
+ * Notify object used by the thread's client, i.e. we will send a
+ * notify signal to this object, expecting the caller to wait on it.
+ */
+extern struct notify audio_output_client_notify;
+
+static inline bool
+audio_output_is_open(const struct audio_output *ao)
+{
+ return ao->open;
+}
+
+static inline bool
+audio_output_command_is_finished(const struct audio_output *ao)
+{
+ return ao->command == AO_COMMAND_NONE;
+}
+
+struct audio_output *
+audio_output_new(const config_param &param,
+ struct player_control *pc,
+ GError **error_r);
+
+bool
+ao_base_init(struct audio_output *ao,
+ const struct audio_output_plugin *plugin,
+ const config_param &param, GError **error_r);
+
+void
+ao_base_finish(struct audio_output *ao);
+
+void
+audio_output_free(struct audio_output *ao);
+
+#endif
diff --git a/src/OutputList.cxx b/src/OutputList.cxx
new file mode 100644
index 000000000..670131ed9
--- /dev/null
+++ b/src/OutputList.cxx
@@ -0,0 +1,100 @@
+/*
+ * 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 "OutputList.hxx"
+#include "OutputAPI.hxx"
+#include "output/AlsaOutputPlugin.hxx"
+#include "output/AoOutputPlugin.hxx"
+#include "output/FifoOutputPlugin.hxx"
+#include "output/HttpdOutputPlugin.hxx"
+#include "output/JackOutputPlugin.hxx"
+#include "output/NullOutputPlugin.hxx"
+#include "output/OpenALOutputPlugin.hxx"
+#include "output/OssOutputPlugin.hxx"
+#include "output/OSXOutputPlugin.hxx"
+#include "output/PipeOutputPlugin.hxx"
+#include "output/PulseOutputPlugin.hxx"
+#include "output/RecorderOutputPlugin.hxx"
+#include "output/RoarOutputPlugin.hxx"
+#include "output/ShoutOutputPlugin.hxx"
+#include "output/SolarisOutputPlugin.hxx"
+#include "output/WinmmOutputPlugin.hxx"
+
+#include <string.h>
+
+const struct audio_output_plugin *const audio_output_plugins[] = {
+#ifdef HAVE_SHOUT
+ &shout_output_plugin,
+#endif
+ &null_output_plugin,
+#ifdef HAVE_FIFO
+ &fifo_output_plugin,
+#endif
+#ifdef ENABLE_PIPE_OUTPUT
+ &pipe_output_plugin,
+#endif
+#ifdef HAVE_ALSA
+ &alsa_output_plugin,
+#endif
+#ifdef HAVE_ROAR
+ &roar_output_plugin,
+#endif
+#ifdef HAVE_AO
+ &ao_output_plugin,
+#endif
+#ifdef HAVE_OSS
+ &oss_output_plugin,
+#endif
+#ifdef HAVE_OPENAL
+ &openal_output_plugin,
+#endif
+#ifdef HAVE_OSX
+ &osx_output_plugin,
+#endif
+#ifdef ENABLE_SOLARIS_OUTPUT
+ &solaris_output_plugin,
+#endif
+#ifdef HAVE_PULSE
+ &pulse_output_plugin,
+#endif
+#ifdef HAVE_JACK
+ &jack_output_plugin,
+#endif
+#ifdef ENABLE_HTTPD_OUTPUT
+ &httpd_output_plugin,
+#endif
+#ifdef ENABLE_RECORDER_OUTPUT
+ &recorder_output_plugin,
+#endif
+#ifdef ENABLE_WINMM_OUTPUT
+ &winmm_output_plugin,
+#endif
+ NULL
+};
+
+const struct audio_output_plugin *
+audio_output_plugin_get(const char *name)
+{
+ audio_output_plugins_for_each(plugin)
+ if (strcmp(plugin->name, name) == 0)
+ return plugin;
+
+ return NULL;
+}
diff --git a/src/OutputList.hxx b/src/OutputList.hxx
new file mode 100644
index 000000000..b7716c67e
--- /dev/null
+++ b/src/OutputList.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_OUTPUT_LIST_HXX
+#define MPD_OUTPUT_LIST_HXX
+
+extern const struct audio_output_plugin *const audio_output_plugins[];
+
+const struct audio_output_plugin *
+audio_output_plugin_get(const char *name);
+
+#define audio_output_plugins_for_each(plugin) \
+ for (const struct audio_output_plugin *plugin, \
+ *const*output_plugin_iterator = &audio_output_plugins[0]; \
+ (plugin = *output_plugin_iterator) != NULL; ++output_plugin_iterator)
+
+#endif
diff --git a/src/OutputPlugin.cxx b/src/OutputPlugin.cxx
new file mode 100644
index 000000000..7ac97cab9
--- /dev/null
+++ b/src/OutputPlugin.cxx
@@ -0,0 +1,109 @@
+/*
+ * 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 "OutputPlugin.hxx"
+#include "OutputInternal.hxx"
+
+struct audio_output *
+ao_plugin_init(const struct audio_output_plugin *plugin,
+ const config_param &param,
+ GError **error)
+{
+ assert(plugin != NULL);
+ assert(plugin->init != NULL);
+
+ return plugin->init(param, error);
+}
+
+void
+ao_plugin_finish(struct audio_output *ao)
+{
+ ao->plugin->finish(ao);
+}
+
+bool
+ao_plugin_enable(struct audio_output *ao, GError **error_r)
+{
+ return ao->plugin->enable != NULL
+ ? ao->plugin->enable(ao, error_r)
+ : true;
+}
+
+void
+ao_plugin_disable(struct audio_output *ao)
+{
+ if (ao->plugin->disable != NULL)
+ ao->plugin->disable(ao);
+}
+
+bool
+ao_plugin_open(struct audio_output *ao, AudioFormat &audio_format,
+ GError **error)
+{
+ return ao->plugin->open(ao, audio_format, error);
+}
+
+void
+ao_plugin_close(struct audio_output *ao)
+{
+ ao->plugin->close(ao);
+}
+
+unsigned
+ao_plugin_delay(struct audio_output *ao)
+{
+ return ao->plugin->delay != NULL
+ ? ao->plugin->delay(ao)
+ : 0;
+}
+
+void
+ao_plugin_send_tag(struct audio_output *ao, const Tag *tag)
+{
+ if (ao->plugin->send_tag != NULL)
+ ao->plugin->send_tag(ao, tag);
+}
+
+size_t
+ao_plugin_play(struct audio_output *ao, const void *chunk, size_t size,
+ GError **error)
+{
+ return ao->plugin->play(ao, chunk, size, error);
+}
+
+void
+ao_plugin_drain(struct audio_output *ao)
+{
+ if (ao->plugin->drain != NULL)
+ ao->plugin->drain(ao);
+}
+
+void
+ao_plugin_cancel(struct audio_output *ao)
+{
+ if (ao->plugin->cancel != NULL)
+ ao->plugin->cancel(ao);
+}
+
+bool
+ao_plugin_pause(struct audio_output *ao)
+{
+ return ao->plugin->pause != NULL && ao->plugin->pause(ao);
+}
diff --git a/src/OutputPlugin.hxx b/src/OutputPlugin.hxx
new file mode 100644
index 000000000..5be476b68
--- /dev/null
+++ b/src/OutputPlugin.hxx
@@ -0,0 +1,210 @@
+/*
+ * 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_OUTPUT_PLUGIN_HXX
+#define MPD_OUTPUT_PLUGIN_HXX
+
+#include "gcc.h"
+#include "gerror.h"
+
+#include <stddef.h>
+
+struct config_param;
+struct AudioFormat;
+struct Tag;
+
+/**
+ * A plugin which controls an audio output device.
+ */
+struct audio_output_plugin {
+ /**
+ * the plugin's name
+ */
+ const char *name;
+
+ /**
+ * Test if this plugin can provide a default output, in case
+ * none has been configured. This method is optional.
+ */
+ bool (*test_default_device)(void);
+
+ /**
+ * Configure and initialize the device, but do not open it
+ * yet.
+ *
+ * @param param the configuration section, or NULL if there is
+ * no configuration
+ * @param error location to store the error occurring, or NULL
+ * to ignore errors
+ * @return NULL on error, or an opaque pointer to the plugin's
+ * data
+ */
+ struct audio_output *(*init)(const config_param &param,
+ GError **error);
+
+ /**
+ * Free resources allocated by this device.
+ */
+ void (*finish)(struct audio_output *data);
+
+ /**
+ * Enable the device. This may allocate resources, preparing
+ * for the device to be opened. Enabling a device cannot
+ * fail: if an error occurs during that, it should be reported
+ * by the open() method.
+ *
+ * @param error_r location to store the error occurring, or
+ * NULL to ignore errors
+ * @return true on success, false on error
+ */
+ bool (*enable)(struct audio_output *data, GError **error_r);
+
+ /**
+ * Disables the device. It is closed before this method is
+ * called.
+ */
+ void (*disable)(struct audio_output *data);
+
+ /**
+ * Really open the device.
+ *
+ * @param audio_format the audio format in which data is going
+ * to be delivered; may be modified by the plugin
+ * @param error location to store the error occurring, or NULL
+ * to ignore errors
+ */
+ bool (*open)(struct audio_output *data, AudioFormat &audio_format,
+ GError **error);
+
+ /**
+ * Close the device.
+ */
+ void (*close)(struct audio_output *data);
+
+ /**
+ * Returns a positive number if the output thread shall delay
+ * the next call to play() or pause(). This should be
+ * implemented instead of doing a sleep inside the plugin,
+ * because this allows MPD to listen to commands meanwhile.
+ *
+ * @return the number of milliseconds to wait
+ */
+ unsigned (*delay)(struct audio_output *data);
+
+ /**
+ * Display metadata for the next chunk. Optional method,
+ * because not all devices can display metadata.
+ */
+ void (*send_tag)(struct audio_output *data, const Tag *tag);
+
+ /**
+ * Play a chunk of audio data.
+ *
+ * @param error location to store the error occurring, or NULL
+ * to ignore errors
+ * @return the number of bytes played, or 0 on error
+ */
+ size_t (*play)(struct audio_output *data,
+ const void *chunk, size_t size,
+ GError **error);
+
+ /**
+ * Wait until the device has finished playing.
+ */
+ void (*drain)(struct audio_output *data);
+
+ /**
+ * Try to cancel data which may still be in the device's
+ * buffers.
+ */
+ void (*cancel)(struct audio_output *data);
+
+ /**
+ * Pause the device. If supported, it may perform a special
+ * action, which keeps the device open, but does not play
+ * anything. Output plugins like "shout" might want to play
+ * silence during pause, so their clients won't be
+ * disconnected. Plugins which do not support pausing will
+ * simply be closed, and have to be reopened when unpaused.
+ *
+ * @return false on error (output will be closed then), true
+ * for continue to pause
+ */
+ bool (*pause)(struct audio_output *data);
+
+ /**
+ * The mixer plugin associated with this output plugin. This
+ * may be NULL if no mixer plugin is implemented. When
+ * created, this mixer plugin gets the same #config_param as
+ * this audio output device.
+ */
+ const struct mixer_plugin *mixer_plugin;
+};
+
+static inline bool
+ao_plugin_test_default_device(const struct audio_output_plugin *plugin)
+{
+ return plugin->test_default_device != NULL
+ ? plugin->test_default_device()
+ : false;
+}
+
+gcc_malloc
+struct audio_output *
+ao_plugin_init(const struct audio_output_plugin *plugin,
+ const config_param &param,
+ GError **error);
+
+void
+ao_plugin_finish(struct audio_output *ao);
+
+bool
+ao_plugin_enable(struct audio_output *ao, GError **error_r);
+
+void
+ao_plugin_disable(struct audio_output *ao);
+
+bool
+ao_plugin_open(struct audio_output *ao, AudioFormat &audio_format,
+ GError **error);
+
+void
+ao_plugin_close(struct audio_output *ao);
+
+gcc_pure
+unsigned
+ao_plugin_delay(struct audio_output *ao);
+
+void
+ao_plugin_send_tag(struct audio_output *ao, const Tag *tag);
+
+size_t
+ao_plugin_play(struct audio_output *ao, const void *chunk, size_t size,
+ GError **error);
+
+void
+ao_plugin_drain(struct audio_output *ao);
+
+void
+ao_plugin_cancel(struct audio_output *ao);
+
+bool
+ao_plugin_pause(struct audio_output *ao);
+
+#endif
diff --git a/src/OutputPrint.cxx b/src/OutputPrint.cxx
new file mode 100644
index 000000000..4e1cf9ced
--- /dev/null
+++ b/src/OutputPrint.cxx
@@ -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.
+ */
+
+/*
+ * Protocol specific code for the audio output library.
+ *
+ */
+
+#include "config.h"
+#include "OutputPrint.hxx"
+#include "OutputAll.hxx"
+#include "OutputInternal.hxx"
+#include "Client.hxx"
+
+void
+printAudioDevices(Client *client)
+{
+ const unsigned n = audio_output_count();
+
+ for (unsigned i = 0; i < n; ++i) {
+ const struct audio_output *ao = audio_output_get(i);
+
+ client_printf(client,
+ "outputid: %i\n"
+ "outputname: %s\n"
+ "outputenabled: %i\n",
+ i, ao->name, ao->enabled);
+ }
+}
diff --git a/src/OutputPrint.hxx b/src/OutputPrint.hxx
new file mode 100644
index 000000000..78717d0af
--- /dev/null
+++ b/src/OutputPrint.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.
+ */
+
+/*
+ * Protocol specific code for the audio output library.
+ *
+ */
+
+#ifndef MPD_OUTPUT_PRINT_HXX
+#define MPD_OUTPUT_PRINT_HXX
+
+class Client;
+
+void
+printAudioDevices(Client *client);
+
+#endif
diff --git a/src/OutputState.cxx b/src/OutputState.cxx
new file mode 100644
index 000000000..776cac8f4
--- /dev/null
+++ b/src/OutputState.cxx
@@ -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.
+ */
+
+/*
+ * Saving and loading the audio output states to/from the state file.
+ *
+ */
+
+#include "config.h"
+#include "OutputState.hxx"
+#include "OutputAll.hxx"
+#include "OutputInternal.hxx"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define AUDIO_DEVICE_STATE "audio_device_state:"
+
+unsigned audio_output_state_version;
+
+void
+audio_output_state_save(FILE *fp)
+{
+ unsigned n = audio_output_count();
+
+ assert(n > 0);
+
+ for (unsigned i = 0; i < n; ++i) {
+ const struct audio_output *ao = audio_output_get(i);
+
+ fprintf(fp, AUDIO_DEVICE_STATE "%d:%s\n",
+ ao->enabled, ao->name);
+ }
+}
+
+bool
+audio_output_state_read(const char *line)
+{
+ long value;
+ char *endptr;
+ const char *name;
+ struct audio_output *ao;
+
+ if (!g_str_has_prefix(line, AUDIO_DEVICE_STATE))
+ return false;
+
+ line += sizeof(AUDIO_DEVICE_STATE) - 1;
+
+ value = strtol(line, &endptr, 10);
+ if (*endptr != ':' || (value != 0 && value != 1))
+ return false;
+
+ if (value != 0)
+ /* state is "enabled": no-op */
+ return true;
+
+ name = endptr + 1;
+ ao = audio_output_find(name);
+ if (ao == NULL) {
+ g_debug("Ignoring device state for '%s'", name);
+ return true;
+ }
+
+ ao->enabled = false;
+ return true;
+}
+
+unsigned
+audio_output_state_get_version(void)
+{
+ return audio_output_state_version;
+}
diff --git a/src/OutputState.hxx b/src/OutputState.hxx
new file mode 100644
index 000000000..5ab765ba8
--- /dev/null
+++ b/src/OutputState.hxx
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+/*
+ * Saving and loading the audio output states to/from the state file.
+ *
+ */
+
+#ifndef MPD_OUTPUT_STATE_HXX
+#define MPD_OUTPUT_STATE_HXX
+
+#include <stdio.h>
+
+bool
+audio_output_state_read(const char *line);
+
+void
+audio_output_state_save(FILE *fp);
+
+/**
+ * Generates a version number for the current state of the audio
+ * outputs. This is used by timer_save_state_file() to determine
+ * whether the state has changed and the state file should be saved.
+ */
+unsigned
+audio_output_state_get_version(void);
+
+#endif
diff --git a/src/OutputThread.cxx b/src/OutputThread.cxx
new file mode 100644
index 000000000..483d35680
--- /dev/null
+++ b/src/OutputThread.cxx
@@ -0,0 +1,681 @@
+/*
+ * 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 "OutputThread.hxx"
+#include "OutputInternal.hxx"
+#include "OutputAPI.hxx"
+#include "pcm/PcmMix.hxx"
+#include "notify.hxx"
+#include "FilterInternal.hxx"
+#include "filter/ConvertFilterPlugin.hxx"
+#include "filter/ReplayGainFilterPlugin.hxx"
+#include "PlayerControl.hxx"
+#include "MusicPipe.hxx"
+#include "MusicChunk.hxx"
+
+#include "mpd_error.h"
+#include "gcc.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "output"
+
+static void ao_command_finished(struct audio_output *ao)
+{
+ assert(ao->command != AO_COMMAND_NONE);
+ ao->command = AO_COMMAND_NONE;
+
+ ao->mutex.unlock();
+ audio_output_client_notify.Signal();
+ ao->mutex.lock();
+}
+
+static bool
+ao_enable(struct audio_output *ao)
+{
+ GError *error = NULL;
+ bool success;
+
+ if (ao->really_enabled)
+ return true;
+
+ ao->mutex.unlock();
+ success = ao_plugin_enable(ao, &error);
+ ao->mutex.lock();
+ if (!success) {
+ g_warning("Failed to enable \"%s\" [%s]: %s\n",
+ ao->name, ao->plugin->name, error->message);
+ g_error_free(error);
+ return false;
+ }
+
+ ao->really_enabled = true;
+ return true;
+}
+
+static void
+ao_close(struct audio_output *ao, bool drain);
+
+static void
+ao_disable(struct audio_output *ao)
+{
+ if (ao->open)
+ ao_close(ao, false);
+
+ if (ao->really_enabled) {
+ ao->really_enabled = false;
+
+ ao->mutex.unlock();
+ ao_plugin_disable(ao);
+ ao->mutex.lock();
+ }
+}
+
+static AudioFormat
+ao_filter_open(struct audio_output *ao, AudioFormat &format,
+ GError **error_r)
+{
+ assert(format.IsValid());
+
+ /* the replay_gain filter cannot fail here */
+ if (ao->replay_gain_filter != NULL)
+ ao->replay_gain_filter->Open(format, error_r);
+ if (ao->other_replay_gain_filter != NULL)
+ ao->other_replay_gain_filter->Open(format, error_r);
+
+ const AudioFormat af = ao->filter->Open(format, error_r);
+ if (!af.IsDefined()) {
+ if (ao->replay_gain_filter != NULL)
+ ao->replay_gain_filter->Close();
+ if (ao->other_replay_gain_filter != NULL)
+ ao->other_replay_gain_filter->Close();
+ }
+
+ return af;
+}
+
+static void
+ao_filter_close(struct audio_output *ao)
+{
+ if (ao->replay_gain_filter != NULL)
+ ao->replay_gain_filter->Close();
+ if (ao->other_replay_gain_filter != NULL)
+ ao->other_replay_gain_filter->Close();
+
+ ao->filter->Close();
+}
+
+static void
+ao_open(struct audio_output *ao)
+{
+ bool success;
+ GError *error = NULL;
+ struct audio_format_string af_string;
+
+ assert(!ao->open);
+ assert(ao->pipe != NULL);
+ assert(ao->chunk == NULL);
+ assert(ao->in_audio_format.IsValid());
+
+ if (ao->fail_timer != NULL) {
+ /* 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 = NULL;
+ }
+
+ /* enable the device (just in case the last enable has failed) */
+
+ if (!ao_enable(ao))
+ /* still no luck */
+ return;
+
+ /* open the filter */
+
+ const AudioFormat filter_audio_format =
+ ao_filter_open(ao, ao->in_audio_format, &error);
+ if (!filter_audio_format.IsDefined()) {
+ g_warning("Failed to open filter for \"%s\" [%s]: %s",
+ ao->name, ao->plugin->name, error->message);
+ g_error_free(error);
+
+ ao->fail_timer = g_timer_new();
+ return;
+ }
+
+ assert(filter_audio_format.IsValid());
+
+ ao->out_audio_format = filter_audio_format;
+ ao->out_audio_format.ApplyMask(ao->config_audio_format);
+
+ ao->mutex.unlock();
+ success = ao_plugin_open(ao, ao->out_audio_format, &error);
+ ao->mutex.lock();
+
+ assert(!ao->open);
+
+ if (!success) {
+ g_warning("Failed to open \"%s\" [%s]: %s",
+ ao->name, ao->plugin->name, error->message);
+ g_error_free(error);
+
+ ao_filter_close(ao);
+ ao->fail_timer = g_timer_new();
+ return;
+ }
+
+ convert_filter_set(ao->convert_filter, ao->out_audio_format);
+
+ ao->open = true;
+
+ g_debug("opened plugin=%s name=\"%s\" "
+ "audio_format=%s",
+ ao->plugin->name, ao->name,
+ audio_format_to_string(ao->out_audio_format, &af_string));
+
+ if (ao->in_audio_format != ao->out_audio_format)
+ g_debug("converting from %s",
+ audio_format_to_string(ao->in_audio_format,
+ &af_string));
+}
+
+static void
+ao_close(struct audio_output *ao, bool drain)
+{
+ assert(ao->open);
+
+ ao->pipe = NULL;
+
+ ao->chunk = NULL;
+ ao->open = false;
+
+ ao->mutex.unlock();
+
+ if (drain)
+ ao_plugin_drain(ao);
+ else
+ ao_plugin_cancel(ao);
+
+ ao_plugin_close(ao);
+ ao_filter_close(ao);
+
+ ao->mutex.lock();
+
+ g_debug("closed plugin=%s name=\"%s\"", ao->plugin->name, ao->name);
+}
+
+static void
+ao_reopen_filter(struct audio_output *ao)
+{
+ GError *error = NULL;
+
+ ao_filter_close(ao);
+ const AudioFormat filter_audio_format =
+ ao_filter_open(ao, ao->in_audio_format, &error);
+ if (!filter_audio_format.IsDefined()) {
+ g_warning("Failed to open filter for \"%s\" [%s]: %s",
+ ao->name, ao->plugin->name, error->message);
+ g_error_free(error);
+
+ /* this is a little code duplication fro ao_close(),
+ but we cannot call this function because we must
+ not call filter_close(ao->filter) again */
+
+ ao->pipe = NULL;
+
+ ao->chunk = NULL;
+ ao->open = false;
+ ao->fail_timer = g_timer_new();
+
+ ao->mutex.unlock();
+ ao_plugin_close(ao);
+ ao->mutex.lock();
+
+ return;
+ }
+
+ convert_filter_set(ao->convert_filter, ao->out_audio_format);
+}
+
+static void
+ao_reopen(struct audio_output *ao)
+{
+ if (!ao->config_audio_format.IsFullyDefined()) {
+ if (ao->open) {
+ const struct music_pipe *mp = ao->pipe;
+ ao_close(ao, true);
+ ao->pipe = mp;
+ }
+
+ /* no audio format is configured: copy in->out, let
+ the output's open() method determine the effective
+ out_audio_format */
+ ao->out_audio_format = ao->in_audio_format;
+ ao->out_audio_format.ApplyMask(ao->config_audio_format);
+ }
+
+ if (ao->open)
+ /* the audio format has changed, and all filters have
+ to be reconfigured */
+ ao_reopen_filter(ao);
+ else
+ ao_open(ao);
+}
+
+/**
+ * Wait until the output's delay reaches zero.
+ *
+ * @return true if playback should be continued, false if a command
+ * was issued
+ */
+static bool
+ao_wait(struct audio_output *ao)
+{
+ while (true) {
+ unsigned delay = ao_plugin_delay(ao);
+ if (delay == 0)
+ return true;
+
+ (void)ao->cond.timed_wait(ao->mutex, delay);
+
+ if (ao->command != AO_COMMAND_NONE)
+ return false;
+ }
+}
+
+static const void *
+ao_chunk_data(struct audio_output *ao, const struct music_chunk *chunk,
+ Filter *replay_gain_filter,
+ unsigned *replay_gain_serial_p,
+ size_t *length_r)
+{
+ assert(chunk != NULL);
+ assert(!chunk->IsEmpty());
+ assert(chunk->CheckFormat(ao->in_audio_format));
+
+ const void *data = chunk->data;
+ size_t length = chunk->length;
+
+ (void)ao;
+
+ assert(length % ao->in_audio_format.GetFrameSize() == 0);
+
+ if (length > 0 && replay_gain_filter != NULL) {
+ if (chunk->replay_gain_serial != *replay_gain_serial_p) {
+ replay_gain_filter_set_info(replay_gain_filter,
+ chunk->replay_gain_serial != 0
+ ? &chunk->replay_gain_info
+ : NULL);
+ *replay_gain_serial_p = chunk->replay_gain_serial;
+ }
+
+ GError *error = NULL;
+ data = replay_gain_filter->FilterPCM(data, length,
+ &length, &error);
+ if (data == NULL) {
+ g_warning("\"%s\" [%s] failed to filter: %s",
+ ao->name, ao->plugin->name, error->message);
+ g_error_free(error);
+ return NULL;
+ }
+ }
+
+ *length_r = length;
+ return data;
+}
+
+static const void *
+ao_filter_chunk(struct audio_output *ao, const struct music_chunk *chunk,
+ size_t *length_r)
+{
+ GError *error = NULL;
+
+ size_t length;
+ const void *data = ao_chunk_data(ao, chunk, ao->replay_gain_filter,
+ &ao->replay_gain_serial, &length);
+ if (data == NULL)
+ return NULL;
+
+ if (length == 0) {
+ /* empty chunk, nothing to do */
+ *length_r = 0;
+ return data;
+ }
+
+ /* cross-fade */
+
+ if (chunk->other != NULL) {
+ size_t other_length;
+ const void *other_data =
+ ao_chunk_data(ao, chunk->other,
+ ao->other_replay_gain_filter,
+ &ao->other_replay_gain_serial,
+ &other_length);
+ if (other_data == NULL)
+ return NULL;
+
+ if (other_length == 0) {
+ *length_r = 0;
+ return data;
+ }
+
+ /* if the "other" chunk is longer, then that trailer
+ is used as-is, without mixing; it is part of the
+ "next" song being faded in, and if there's a rest,
+ it means cross-fading ends here */
+
+ if (length > other_length)
+ length = other_length;
+
+ void *dest = ao->cross_fade_buffer.Get(other_length);
+ memcpy(dest, other_data, other_length);
+ if (!pcm_mix(dest, data, length,
+ ao->in_audio_format.format,
+ 1.0 - chunk->mix_ratio)) {
+ g_warning("Cannot cross-fade format %s",
+ sample_format_to_string(ao->in_audio_format.format));
+ return NULL;
+ }
+
+ data = dest;
+ length = other_length;
+ }
+
+ /* apply filter chain */
+
+ data = ao->filter->FilterPCM(data, length, &length, &error);
+ if (data == NULL) {
+ g_warning("\"%s\" [%s] failed to filter: %s",
+ ao->name, ao->plugin->name, error->message);
+ g_error_free(error);
+ return NULL;
+ }
+
+ *length_r = length;
+ return data;
+}
+
+static bool
+ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk)
+{
+ GError *error = NULL;
+
+ assert(ao != NULL);
+ assert(ao->filter != NULL);
+
+ if (ao->tags && gcc_unlikely(chunk->tag != NULL)) {
+ ao->mutex.unlock();
+ ao_plugin_send_tag(ao, chunk->tag);
+ ao->mutex.lock();
+ }
+
+ size_t size;
+#if GCC_CHECK_VERSION(4,7)
+ /* workaround -Wmaybe-uninitialized false positive */
+ size = 0;
+#endif
+ const char *data = (const char *)ao_filter_chunk(ao, chunk, &size);
+ if (data == NULL) {
+ ao_close(ao, false);
+
+ /* don't automatically reopen this device for 10
+ seconds */
+ ao->fail_timer = g_timer_new();
+ return false;
+ }
+
+ while (size > 0 && ao->command == AO_COMMAND_NONE) {
+ size_t nbytes;
+
+ if (!ao_wait(ao))
+ break;
+
+ ao->mutex.unlock();
+ nbytes = ao_plugin_play(ao, data, size, &error);
+ ao->mutex.lock();
+ if (nbytes == 0) {
+ /* play()==0 means failure */
+ g_warning("\"%s\" [%s] failed to play: %s",
+ ao->name, ao->plugin->name, error->message);
+ g_error_free(error);
+
+ ao_close(ao, false);
+
+ /* don't automatically reopen this device for
+ 10 seconds */
+ assert(ao->fail_timer == NULL);
+ ao->fail_timer = g_timer_new();
+
+ return false;
+ }
+
+ assert(nbytes <= size);
+ assert(nbytes % ao->out_audio_format.GetFrameSize() == 0);
+
+ data += nbytes;
+ size -= nbytes;
+ }
+
+ return true;
+}
+
+static const struct music_chunk *
+ao_next_chunk(struct audio_output *ao)
+{
+ return ao->chunk != NULL
+ /* continue the previous play() call */
+ ? ao->chunk->next
+ /* get the first chunk from the pipe */
+ : music_pipe_peek(ao->pipe);
+}
+
+/**
+ * Plays all remaining chunks, until the tail of the pipe has been
+ * reached (and no more chunks are queued), or until a command is
+ * received.
+ *
+ * @return true if at least one chunk has been available, false if the
+ * tail of the pipe was already reached
+ */
+static bool
+ao_play(struct audio_output *ao)
+{
+ bool success;
+ const struct music_chunk *chunk;
+
+ assert(ao->pipe != NULL);
+
+ chunk = ao_next_chunk(ao);
+ if (chunk == NULL)
+ /* no chunk available */
+ return false;
+
+ ao->chunk_finished = false;
+
+ while (chunk != NULL && ao->command == AO_COMMAND_NONE) {
+ assert(!ao->chunk_finished);
+
+ ao->chunk = chunk;
+
+ success = ao_play_chunk(ao, chunk);
+ if (!success) {
+ assert(ao->chunk == NULL);
+ break;
+ }
+
+ assert(ao->chunk == chunk);
+ chunk = chunk->next;
+ }
+
+ ao->chunk_finished = true;
+
+ ao->mutex.unlock();
+ ao->player_control->LockSignal();
+ ao->mutex.lock();
+
+ return true;
+}
+
+static void ao_pause(struct audio_output *ao)
+{
+ bool ret;
+
+ ao->mutex.unlock();
+ ao_plugin_cancel(ao);
+ ao->mutex.lock();
+
+ ao->pause = true;
+ ao_command_finished(ao);
+
+ do {
+ if (!ao_wait(ao))
+ break;
+
+ ao->mutex.unlock();
+ ret = ao_plugin_pause(ao);
+ ao->mutex.lock();
+
+ if (!ret) {
+ ao_close(ao, false);
+ break;
+ }
+ } while (ao->command == AO_COMMAND_NONE);
+
+ ao->pause = false;
+}
+
+static gpointer audio_output_task(gpointer arg)
+{
+ struct audio_output *ao = (struct audio_output *)arg;
+
+ ao->mutex.lock();
+
+ while (1) {
+ switch (ao->command) {
+ case AO_COMMAND_NONE:
+ break;
+
+ case AO_COMMAND_ENABLE:
+ ao_enable(ao);
+ ao_command_finished(ao);
+ break;
+
+ case AO_COMMAND_DISABLE:
+ ao_disable(ao);
+ ao_command_finished(ao);
+ break;
+
+ case AO_COMMAND_OPEN:
+ ao_open(ao);
+ ao_command_finished(ao);
+ break;
+
+ case AO_COMMAND_REOPEN:
+ ao_reopen(ao);
+ ao_command_finished(ao);
+ break;
+
+ case AO_COMMAND_CLOSE:
+ assert(ao->open);
+ assert(ao->pipe != NULL);
+
+ ao_close(ao, false);
+ ao_command_finished(ao);
+ break;
+
+ case AO_COMMAND_PAUSE:
+ if (!ao->open) {
+ /* the output has failed after
+ audio_output_all_pause() has
+ submitted the PAUSE command; bail
+ out */
+ ao_command_finished(ao);
+ break;
+ }
+
+ ao_pause(ao);
+ /* don't "break" here: this might cause
+ ao_play() to be called when command==CLOSE
+ ends the paused state - "continue" checks
+ the new command first */
+ continue;
+
+ case AO_COMMAND_DRAIN:
+ if (ao->open) {
+ assert(ao->chunk == NULL);
+ assert(music_pipe_peek(ao->pipe) == NULL);
+
+ ao->mutex.unlock();
+ ao_plugin_drain(ao);
+ ao->mutex.lock();
+ }
+
+ ao_command_finished(ao);
+ continue;
+
+ case AO_COMMAND_CANCEL:
+ ao->chunk = NULL;
+
+ if (ao->open) {
+ ao->mutex.unlock();
+ ao_plugin_cancel(ao);
+ ao->mutex.lock();
+ }
+
+ ao_command_finished(ao);
+ continue;
+
+ case AO_COMMAND_KILL:
+ ao->chunk = NULL;
+ ao_command_finished(ao);
+ ao->mutex.unlock();
+ return NULL;
+ }
+
+ if (ao->open && ao->allow_play && ao_play(ao))
+ /* don't wait for an event if there are more
+ chunks in the pipe */
+ continue;
+
+ if (ao->command == AO_COMMAND_NONE)
+ ao->cond.wait(ao->mutex);
+ }
+}
+
+void audio_output_thread_start(struct audio_output *ao)
+{
+ assert(ao->command == AO_COMMAND_NONE);
+
+#if GLIB_CHECK_VERSION(2,32,0)
+ ao->thread = g_thread_new("output", audio_output_task, ao);
+#else
+ GError *e = nullptr;
+ if (!(ao->thread = g_thread_create(audio_output_task, ao, true, &e)))
+ MPD_ERROR("Failed to spawn output task: %s\n", e->message);
+#endif
+}
diff --git a/src/OutputThread.hxx b/src/OutputThread.hxx
new file mode 100644
index 000000000..1a7932162
--- /dev/null
+++ b/src/OutputThread.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_OUTPUT_THREAD_HXX
+#define MPD_OUTPUT_THREAD_HXX
+
+struct audio_output;
+
+void audio_output_thread_start(struct audio_output *ao);
+
+#endif
diff --git a/src/Page.cxx b/src/Page.cxx
new file mode 100644
index 000000000..bf30376e4
--- /dev/null
+++ b/src/Page.cxx
@@ -0,0 +1,70 @@
+/*
+ * 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.
+ */
+
+#include "config.h"
+#include "Page.hxx"
+
+#include <glib.h>
+
+#include <new>
+
+#include <assert.h>
+#include <string.h>
+
+Page *
+Page::Create(size_t size)
+{
+ void *p = g_malloc(sizeof(Page) + size -
+ sizeof(Page::data));
+ return ::new(p) Page(size);
+}
+
+Page *
+Page::Copy(const void *data, size_t size)
+{
+ assert(data != nullptr);
+
+ Page *page = Create(size);
+ memcpy(page->data, data, size);
+ return page;
+}
+
+Page *
+Page::Concat(const Page &a, const Page &b)
+{
+ Page *page = Create(a.size + b.size);
+
+ memcpy(page->data, a.data, a.size);
+ memcpy(page->data + a.size, b.data, b.size);
+
+ return page;
+}
+
+bool
+Page::Unref()
+{
+ bool unused = ref.Decrement();
+
+ if (unused) {
+ this->Page::~Page();
+ g_free(this);
+ }
+
+ return unused;
+}
diff --git a/src/Page.hxx b/src/Page.hxx
new file mode 100644
index 000000000..2bc9a6ac5
--- /dev/null
+++ b/src/Page.hxx
@@ -0,0 +1,104 @@
+/*
+ * 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.
+ */
+
+/** \file
+ *
+ * This is a library which manages reference counted buffers.
+ */
+
+#ifndef MPD_PAGE_HXX
+#define MPD_PAGE_HXX
+
+#include "util/RefCount.hxx"
+
+#include <algorithm>
+
+#include <stddef.h>
+
+/**
+ * A dynamically allocated buffer which keeps track of its reference
+ * count. This is useful for passing buffers around, when several
+ * instances hold references to one buffer.
+ */
+class Page {
+ /**
+ * The number of references to this buffer. This library uses
+ * atomic functions to access it, i.e. no locks are required.
+ * As soon as this attribute reaches zero, the buffer is
+ * freed.
+ */
+ RefCount ref;
+
+public:
+ /**
+ * The size of this buffer in bytes.
+ */
+ const size_t size;
+
+ /**
+ * Dynamic array containing the buffer data.
+ */
+ unsigned char data[sizeof(long)];
+
+protected:
+ Page(size_t _size):size(_size) {}
+ ~Page() = default;
+
+ /**
+ * Allocates a new #Page object, without filling the data
+ * element.
+ */
+ static Page *Create(size_t size);
+
+public:
+ /**
+ * Creates a new #page object, and copies data from the
+ * specified buffer. It is initialized with a reference count
+ * of 1.
+ *
+ * @param data the source buffer
+ * @param size the size of the source buffer
+ */
+ static Page *Copy(const void *data, size_t size);
+
+ /**
+ * Concatenates two pages to a new page.
+ *
+ * @param a the first page
+ * @param b the second page, which is appended
+ */
+ static Page *Concat(const Page &a, const Page &b);
+
+ /**
+ * Increases the reference counter.
+ */
+ void Ref() {
+ ref.Increment();
+ }
+
+ /**
+ * Decreases the reference counter. If it reaches zero, the #page is
+ * freed.
+ *
+ * @return true if the #page has been freed
+ */
+ bool Unref();
+};
+
+#endif
diff --git a/src/Partition.hxx b/src/Partition.hxx
new file mode 100644
index 000000000..0d5017985
--- /dev/null
+++ b/src/Partition.hxx
@@ -0,0 +1,170 @@
+/*
+ * 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_PARTITION_HXX
+#define MPD_PARTITION_HXX
+
+#include "Playlist.hxx"
+#include "PlayerControl.hxx"
+
+struct Instance;
+
+/**
+ * A partition of the Music Player Daemon. It is a separate unit with
+ * a playlist, a player, outputs etc.
+ */
+struct Partition {
+ Instance &instance;
+
+ struct playlist playlist;
+
+ player_control pc;
+
+ Partition(Instance &_instance,
+ unsigned max_length,
+ unsigned buffer_chunks,
+ unsigned buffered_before_play)
+ :instance(_instance), playlist(max_length),
+ pc(buffer_chunks, buffered_before_play) {
+ }
+
+ void ClearQueue() {
+ playlist.Clear(pc);
+ }
+
+ enum playlist_result AppendFile(const char *path_utf8,
+ unsigned *added_id=nullptr) {
+ return playlist.AppendFile(pc, path_utf8, added_id);
+ }
+
+ enum playlist_result AppendURI(const char *uri_utf8,
+ unsigned *added_id=nullptr) {
+ return playlist.AppendURI(pc, uri_utf8, added_id);
+ }
+
+ enum playlist_result DeletePosition(unsigned position) {
+ return playlist.DeletePosition(pc, position);
+ }
+
+ enum playlist_result DeleteId(unsigned id) {
+ return playlist.DeleteId(pc, id);
+ }
+
+ /**
+ * Deletes a range of songs from the playlist.
+ *
+ * @param start the position of the first song to delete
+ * @param end the position after the last song to delete
+ */
+ enum playlist_result DeleteRange(unsigned start, unsigned end) {
+ return playlist.DeleteRange(pc, start, end);
+ }
+
+ void DeleteSong(const Song &song) {
+ playlist.DeleteSong(pc, song);
+ }
+
+ void Shuffle(unsigned start, unsigned end) {
+ playlist.Shuffle(pc, start, end);
+ }
+
+ enum playlist_result MoveRange(unsigned start, unsigned end, int to) {
+ return playlist.MoveRange(pc, start, end, to);
+ }
+
+ enum playlist_result MoveId(unsigned id, int to) {
+ return playlist.MoveId(pc, id, to);
+ }
+
+ enum playlist_result SwapPositions(unsigned song1, unsigned song2) {
+ return playlist.SwapPositions(pc, song1, song2);
+ }
+
+ enum playlist_result SwapIds(unsigned id1, unsigned id2) {
+ return playlist.SwapIds(pc, id1, id2);
+ }
+
+ enum playlist_result SetPriorityRange(unsigned start_position,
+ unsigned end_position,
+ uint8_t priority) {
+ return playlist.SetPriorityRange(pc,
+ start_position, end_position,
+ priority);
+ }
+
+ enum playlist_result SetPriorityId(unsigned song_id,
+ uint8_t priority) {
+ return playlist.SetPriorityId(pc, song_id, priority);
+ }
+
+ void Stop() {
+ playlist.Stop(pc);
+ }
+
+ enum playlist_result PlayPosition(int position) {
+ return playlist.PlayPosition(pc, position);
+ }
+
+ enum playlist_result PlayId(int id) {
+ return playlist.PlayId(pc, id);
+ }
+
+ void PlayNext() {
+ return playlist.PlayNext(pc);
+ }
+
+ void PlayPrevious() {
+ return playlist.PlayPrevious(pc);
+ }
+
+ enum playlist_result SeekSongPosition(unsigned song_position,
+ float seek_time) {
+ return playlist.SeekSongPosition(pc, song_position, seek_time);
+ }
+
+ enum playlist_result SeekSongId(unsigned song_id, float seek_time) {
+ return playlist.SeekSongId(pc, song_id, seek_time);
+ }
+
+ enum playlist_result SeekCurrent(float seek_time, bool relative) {
+ return playlist.SeekCurrent(pc, seek_time, relative);
+ }
+
+ void SetRepeat(bool new_value) {
+ playlist.SetRepeat(pc, new_value);
+ }
+
+ bool GetRandom() const {
+ return playlist.GetRandom();
+ }
+
+ void SetRandom(bool new_value) {
+ playlist.SetRandom(pc, new_value);
+ }
+
+ void SetSingle(bool new_value) {
+ playlist.SetSingle(pc, new_value);
+ }
+
+ void SetConsume(bool new_value) {
+ playlist.SetConsume(new_value);
+ }
+};
+
+#endif
diff --git a/src/Permission.cxx b/src/Permission.cxx
new file mode 100644
index 000000000..246047774
--- /dev/null
+++ b/src/Permission.cxx
@@ -0,0 +1,127 @@
+/*
+ * 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 "Permission.hxx"
+#include "mpd_error.h"
+#include "conf.h"
+
+#include <map>
+#include <string>
+
+#include <glib.h>
+
+#include <string.h>
+
+#define PERMISSION_PASSWORD_CHAR '@'
+#define PERMISSION_SEPERATOR ","
+
+#define PERMISSION_READ_STRING "read"
+#define PERMISSION_ADD_STRING "add"
+#define PERMISSION_CONTROL_STRING "control"
+#define PERMISSION_ADMIN_STRING "admin"
+
+static std::map<std::string, unsigned> permission_passwords;
+
+static unsigned permission_default;
+
+static unsigned parsePermissions(const char *string)
+{
+ unsigned permission = 0;
+ gchar **tokens;
+
+ if (!string)
+ return 0;
+
+ tokens = g_strsplit(string, PERMISSION_SEPERATOR, 0);
+ for (unsigned i = 0; tokens[i] != NULL; ++i) {
+ char *temp = tokens[i];
+
+ if (strcmp(temp, PERMISSION_READ_STRING) == 0) {
+ permission |= PERMISSION_READ;
+ } else if (strcmp(temp, PERMISSION_ADD_STRING) == 0) {
+ permission |= PERMISSION_ADD;
+ } else if (strcmp(temp, PERMISSION_CONTROL_STRING) == 0) {
+ permission |= PERMISSION_CONTROL;
+ } else if (strcmp(temp, PERMISSION_ADMIN_STRING) == 0) {
+ permission |= PERMISSION_ADMIN;
+ } else {
+ MPD_ERROR("unknown permission \"%s\"", temp);
+ }
+ }
+
+ g_strfreev(tokens);
+
+ return permission;
+}
+
+void initPermissions(void)
+{
+ char *password;
+ unsigned permission;
+ const struct config_param *param;
+
+ permission_default = PERMISSION_READ | PERMISSION_ADD |
+ PERMISSION_CONTROL | PERMISSION_ADMIN;
+
+ param = config_get_next_param(CONF_PASSWORD, NULL);
+
+ if (param) {
+ permission_default = 0;
+
+ do {
+ const char *separator =
+ strchr(param->value, PERMISSION_PASSWORD_CHAR);
+
+ if (separator == NULL)
+ MPD_ERROR("\"%c\" not found in password string "
+ "\"%s\", line %i",
+ PERMISSION_PASSWORD_CHAR,
+ param->value, param->line);
+
+ password = g_strndup(param->value,
+ separator - param->value);
+
+ permission = parsePermissions(separator + 1);
+
+ permission_passwords.insert(std::make_pair(password,
+ permission));
+ } while ((param = config_get_next_param(CONF_PASSWORD, param)));
+ }
+
+ param = config_get_param(CONF_DEFAULT_PERMS);
+
+ if (param)
+ permission_default = parsePermissions(param->value);
+}
+
+int getPermissionFromPassword(char const* password, unsigned* permission)
+{
+ auto i = permission_passwords.find(password);
+ if (i == permission_passwords.end())
+ return -1;
+
+ *permission = i->second;
+ return 0;
+}
+
+unsigned getDefaultPermissions(void)
+{
+ return permission_default;
+}
diff --git a/src/Permission.hxx b/src/Permission.hxx
new file mode 100644
index 000000000..4ff3850e0
--- /dev/null
+++ b/src/Permission.hxx
@@ -0,0 +1,36 @@
+/*
+ * 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_PERMISSION_HXX
+#define MPD_PERMISSION_HXX
+
+#define PERMISSION_NONE 0
+#define PERMISSION_READ 1
+#define PERMISSION_ADD 2
+#define PERMISSION_CONTROL 4
+#define PERMISSION_ADMIN 8
+
+
+int getPermissionFromPassword(char const* password, unsigned* permission);
+
+unsigned getDefaultPermissions(void);
+
+void initPermissions(void);
+
+#endif
diff --git a/src/PlayerCommands.cxx b/src/PlayerCommands.cxx
new file mode 100644
index 000000000..0d4089940
--- /dev/null
+++ b/src/PlayerCommands.cxx
@@ -0,0 +1,396 @@
+/*
+ * 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 "PlayerCommands.hxx"
+#include "CommandError.hxx"
+#include "Playlist.hxx"
+#include "PlaylistPrint.hxx"
+#include "UpdateGlue.hxx"
+#include "ClientInternal.hxx"
+#include "Volume.hxx"
+#include "OutputAll.hxx"
+#include "Partition.hxx"
+#include "protocol/Result.hxx"
+#include "protocol/ArgParser.hxx"
+
+extern "C" {
+#include "AudioFormat.hxx"
+}
+
+#include "replay_gain_config.h"
+
+#include <errno.h>
+
+#define COMMAND_STATUS_STATE "state"
+#define COMMAND_STATUS_REPEAT "repeat"
+#define COMMAND_STATUS_SINGLE "single"
+#define COMMAND_STATUS_CONSUME "consume"
+#define COMMAND_STATUS_RANDOM "random"
+#define COMMAND_STATUS_PLAYLIST "playlist"
+#define COMMAND_STATUS_PLAYLIST_LENGTH "playlistlength"
+#define COMMAND_STATUS_SONG "song"
+#define COMMAND_STATUS_SONGID "songid"
+#define COMMAND_STATUS_NEXTSONG "nextsong"
+#define COMMAND_STATUS_NEXTSONGID "nextsongid"
+#define COMMAND_STATUS_TIME "time"
+#define COMMAND_STATUS_BITRATE "bitrate"
+#define COMMAND_STATUS_ERROR "error"
+#define COMMAND_STATUS_CROSSFADE "xfade"
+#define COMMAND_STATUS_MIXRAMPDB "mixrampdb"
+#define COMMAND_STATUS_MIXRAMPDELAY "mixrampdelay"
+#define COMMAND_STATUS_AUDIO "audio"
+#define COMMAND_STATUS_UPDATING_DB "updating_db"
+
+enum command_return
+handle_play(Client *client, int argc, char *argv[])
+{
+ int song = -1;
+
+ if (argc == 2 && !check_int(client, &song, argv[1]))
+ return COMMAND_RETURN_ERROR;
+ enum playlist_result result = client->partition.PlayPosition(song);
+ return print_playlist_result(client, result);
+}
+
+enum command_return
+handle_playid(Client *client, int argc, char *argv[])
+{
+ int id = -1;
+
+ if (argc == 2 && !check_int(client, &id, argv[1]))
+ return COMMAND_RETURN_ERROR;
+
+ enum playlist_result result = client->partition.PlayId(id);
+ return print_playlist_result(client, result);
+}
+
+enum command_return
+handle_stop(Client *client,
+ G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
+{
+ client->partition.Stop();
+ return COMMAND_RETURN_OK;
+}
+
+enum command_return
+handle_currentsong(Client *client,
+ G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
+{
+ playlist_print_current(client, &client->playlist);
+ return COMMAND_RETURN_OK;
+}
+
+enum command_return
+handle_pause(Client *client,
+ int argc, char *argv[])
+{
+ if (argc == 2) {
+ bool pause_flag;
+ if (!check_bool(client, &pause_flag, argv[1]))
+ return COMMAND_RETURN_ERROR;
+
+ client->player_control->SetPause(pause_flag);
+ } else
+ client->player_control->Pause();
+
+ return COMMAND_RETURN_OK;
+}
+
+enum command_return
+handle_status(Client *client,
+ G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
+{
+ const char *state = NULL;
+ int updateJobId;
+ int song;
+
+ const auto player_status = client->player_control->GetStatus();
+
+ switch (player_status.state) {
+ case PLAYER_STATE_STOP:
+ state = "stop";
+ break;
+ case PLAYER_STATE_PAUSE:
+ state = "pause";
+ break;
+ case PLAYER_STATE_PLAY:
+ state = "play";
+ break;
+ }
+
+ const playlist &playlist = client->playlist;
+ client_printf(client,
+ "volume: %i\n"
+ COMMAND_STATUS_REPEAT ": %i\n"
+ COMMAND_STATUS_RANDOM ": %i\n"
+ COMMAND_STATUS_SINGLE ": %i\n"
+ COMMAND_STATUS_CONSUME ": %i\n"
+ COMMAND_STATUS_PLAYLIST ": %li\n"
+ COMMAND_STATUS_PLAYLIST_LENGTH ": %i\n"
+ COMMAND_STATUS_CROSSFADE ": %i\n"
+ COMMAND_STATUS_MIXRAMPDB ": %f\n"
+ COMMAND_STATUS_MIXRAMPDELAY ": %f\n"
+ COMMAND_STATUS_STATE ": %s\n",
+ volume_level_get(),
+ playlist.GetRepeat(),
+ playlist.GetRandom(),
+ playlist.GetSingle(),
+ playlist.GetConsume(),
+ (unsigned long)playlist.GetVersion(),
+ playlist.GetLength(),
+ (int)(client->player_control->GetCrossFade() + 0.5),
+ client->player_control->GetMixRampDb(),
+ client->player_control->GetMixRampDelay(),
+ state);
+
+ song = playlist.GetCurrentPosition();
+ if (song >= 0) {
+ client_printf(client,
+ COMMAND_STATUS_SONG ": %i\n"
+ COMMAND_STATUS_SONGID ": %u\n",
+ song, playlist.PositionToId(song));
+ }
+
+ if (player_status.state != PLAYER_STATE_STOP) {
+ client_printf(client,
+ COMMAND_STATUS_TIME ": %i:%i\n"
+ "elapsed: %1.3f\n"
+ COMMAND_STATUS_BITRATE ": %u\n",
+ (int)(player_status.elapsed_time + 0.5),
+ (int)(player_status.total_time + 0.5),
+ player_status.elapsed_time,
+ player_status.bit_rate);
+
+ if (player_status.audio_format.IsDefined()) {
+ struct audio_format_string af_string;
+
+ client_printf(client,
+ COMMAND_STATUS_AUDIO ": %s\n",
+ audio_format_to_string(player_status.audio_format,
+ &af_string));
+ }
+ }
+
+ if ((updateJobId = isUpdatingDB())) {
+ client_printf(client,
+ COMMAND_STATUS_UPDATING_DB ": %i\n",
+ updateJobId);
+ }
+
+ char *error = client->player_control->GetErrorMessage();
+ if (error != NULL) {
+ client_printf(client,
+ COMMAND_STATUS_ERROR ": %s\n",
+ error);
+ g_free(error);
+ }
+
+ song = playlist.GetNextPosition();
+ if (song >= 0) {
+ client_printf(client,
+ COMMAND_STATUS_NEXTSONG ": %i\n"
+ COMMAND_STATUS_NEXTSONGID ": %u\n",
+ song, playlist.PositionToId(song));
+ }
+
+ return COMMAND_RETURN_OK;
+}
+
+enum command_return
+handle_next(Client *client,
+ G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
+{
+ playlist &playlist = client->playlist;
+
+ /* single mode is not considered when this is user who
+ * wants to change song. */
+ const bool single = playlist.queue.single;
+ playlist.queue.single = false;
+
+ client->partition.PlayNext();
+
+ playlist.queue.single = single;
+ return COMMAND_RETURN_OK;
+}
+
+enum command_return
+handle_previous(Client *client,
+ G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
+{
+ client->partition.PlayPrevious();
+ return COMMAND_RETURN_OK;
+}
+
+enum command_return
+handle_repeat(Client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ bool status;
+ if (!check_bool(client, &status, argv[1]))
+ return COMMAND_RETURN_ERROR;
+
+ client->partition.SetRepeat(status);
+ return COMMAND_RETURN_OK;
+}
+
+enum command_return
+handle_single(Client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ bool status;
+ if (!check_bool(client, &status, argv[1]))
+ return COMMAND_RETURN_ERROR;
+
+ client->partition.SetSingle(status);
+ return COMMAND_RETURN_OK;
+}
+
+enum command_return
+handle_consume(Client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ bool status;
+ if (!check_bool(client, &status, argv[1]))
+ return COMMAND_RETURN_ERROR;
+
+ client->partition.SetConsume(status);
+ return COMMAND_RETURN_OK;
+}
+
+enum command_return
+handle_random(Client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ bool status;
+ if (!check_bool(client, &status, argv[1]))
+ return COMMAND_RETURN_ERROR;
+
+ client->partition.SetRandom(status);
+ audio_output_all_set_replay_gain_mode(replay_gain_get_real_mode(client->partition.GetRandom()));
+ return COMMAND_RETURN_OK;
+}
+
+enum command_return
+handle_clearerror(G_GNUC_UNUSED Client *client,
+ G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
+{
+ client->player_control->ClearError();
+ return COMMAND_RETURN_OK;
+}
+
+enum command_return
+handle_seek(Client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ unsigned song, seek_time;
+
+ if (!check_unsigned(client, &song, argv[1]))
+ return COMMAND_RETURN_ERROR;
+ if (!check_unsigned(client, &seek_time, argv[2]))
+ return COMMAND_RETURN_ERROR;
+
+ enum playlist_result result =
+ client->partition.SeekSongPosition(song, seek_time);
+ return print_playlist_result(client, result);
+}
+
+enum command_return
+handle_seekid(Client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ unsigned id, seek_time;
+
+ if (!check_unsigned(client, &id, argv[1]))
+ return COMMAND_RETURN_ERROR;
+ if (!check_unsigned(client, &seek_time, argv[2]))
+ return COMMAND_RETURN_ERROR;
+
+ enum playlist_result result =
+ client->partition.SeekSongId(id, seek_time);
+ return print_playlist_result(client, result);
+}
+
+enum command_return
+handle_seekcur(Client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ const char *p = argv[1];
+ bool relative = *p == '+' || *p == '-';
+ int seek_time;
+ if (!check_int(client, &seek_time, p))
+ return COMMAND_RETURN_ERROR;
+
+ enum playlist_result result =
+ client->partition.SeekCurrent(seek_time, relative);
+ return print_playlist_result(client, result);
+}
+
+enum command_return
+handle_crossfade(Client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ unsigned xfade_time;
+
+ if (!check_unsigned(client, &xfade_time, argv[1]))
+ return COMMAND_RETURN_ERROR;
+ client->player_control->SetCrossFade(xfade_time);
+
+ return COMMAND_RETURN_OK;
+}
+
+enum command_return
+handle_mixrampdb(Client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ float db;
+
+ if (!check_float(client, &db, argv[1]))
+ return COMMAND_RETURN_ERROR;
+ client->player_control->SetMixRampDb(db);
+
+ return COMMAND_RETURN_OK;
+}
+
+enum command_return
+handle_mixrampdelay(Client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ float delay_secs;
+
+ if (!check_float(client, &delay_secs, argv[1]))
+ return COMMAND_RETURN_ERROR;
+ client->player_control->SetMixRampDelay(delay_secs);
+
+ return COMMAND_RETURN_OK;
+}
+
+enum command_return
+handle_replay_gain_mode(Client *client,
+ G_GNUC_UNUSED int argc, char *argv[])
+{
+ if (!replay_gain_set_mode_string(argv[1])) {
+ command_error(client, ACK_ERROR_ARG,
+ "Unrecognized replay gain mode");
+ return COMMAND_RETURN_ERROR;
+ }
+
+ audio_output_all_set_replay_gain_mode(replay_gain_get_real_mode(client->playlist.queue.random));
+
+ return COMMAND_RETURN_OK;
+}
+
+enum command_return
+handle_replay_gain_status(Client *client,
+ G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
+{
+ client_printf(client, "replay_gain_mode: %s\n",
+ replay_gain_get_mode_string());
+ return COMMAND_RETURN_OK;
+}
diff --git a/src/PlayerCommands.hxx b/src/PlayerCommands.hxx
new file mode 100644
index 000000000..a2fed5853
--- /dev/null
+++ b/src/PlayerCommands.hxx
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ */
+
+#ifndef MPD_PLAYER_COMMANDS_HXX
+#define MPD_PLAYER_COMMANDS_HXX
+
+#include "command.h"
+
+class Client;
+
+enum command_return
+handle_play(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_playid(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_stop(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_currentsong(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_pause(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_status(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_next(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_previous(Client *client, int argc, char *avg[]);
+
+enum command_return
+handle_repeat(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_single(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_consume(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_random(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_clearerror(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_seek(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_seekid(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_seekcur(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_crossfade(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_mixrampdb(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_mixrampdelay(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_replay_gain_mode(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_replay_gain_status(Client *client, int argc, char *argv[]);
+
+#endif
diff --git a/src/PlayerControl.cxx b/src/PlayerControl.cxx
new file mode 100644
index 000000000..357cdfc17
--- /dev/null
+++ b/src/PlayerControl.cxx
@@ -0,0 +1,321 @@
+/*
+ * 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 "PlayerControl.hxx"
+#include "Idle.hxx"
+#include "Song.hxx"
+#include "DecoderControl.hxx"
+#include "Main.hxx"
+
+#include <cmath>
+
+#include <assert.h>
+#include <stdio.h>
+
+static void
+pc_enqueue_song_locked(struct player_control *pc, Song *song);
+
+player_control::player_control(unsigned _buffer_chunks,
+ unsigned _buffered_before_play)
+ :buffer_chunks(_buffer_chunks),
+ buffered_before_play(_buffered_before_play),
+ thread(nullptr),
+ command(PLAYER_COMMAND_NONE),
+ state(PLAYER_STATE_STOP),
+ error_type(PLAYER_ERROR_NONE),
+ error(nullptr),
+ next_song(nullptr),
+ cross_fade_seconds(0),
+ mixramp_db(0),
+#if defined(__GLIBCXX__) && !defined(_GLIBCXX_USE_C99_MATH_TR1)
+ /* workaround: on MacPorts, this option is disabled on gcc47,
+ and therefore std::nanf() is not available */
+ mixramp_delay_seconds(nanf("")),
+#else
+ mixramp_delay_seconds(std::nanf("")),
+#endif
+ total_play_time(0),
+ border_pause(false)
+{
+}
+
+player_control::~player_control()
+{
+ if (next_song != nullptr)
+ next_song->Free();
+}
+
+static void
+player_command_wait_locked(struct player_control *pc)
+{
+ while (pc->command != PLAYER_COMMAND_NONE)
+ pc->ClientWait();
+}
+
+static void
+player_command_locked(struct player_control *pc, enum player_command cmd)
+{
+ assert(pc->command == PLAYER_COMMAND_NONE);
+
+ pc->command = cmd;
+ pc->Signal();
+ player_command_wait_locked(pc);
+}
+
+static void
+player_command(struct player_control *pc, enum player_command cmd)
+{
+ pc->Lock();
+ player_command_locked(pc, cmd);
+ pc->Unlock();
+}
+
+void
+player_control::Play(Song *song)
+{
+ assert(song != NULL);
+
+ Lock();
+
+ if (state != PLAYER_STATE_STOP)
+ player_command_locked(this, PLAYER_COMMAND_STOP);
+
+ assert(next_song == nullptr);
+
+ pc_enqueue_song_locked(this, song);
+
+ assert(next_song == nullptr);
+
+ Unlock();
+}
+
+void
+player_control::Cancel()
+{
+ player_command(this, PLAYER_COMMAND_CANCEL);
+ assert(next_song == NULL);
+}
+
+void
+player_control::Stop()
+{
+ player_command(this, PLAYER_COMMAND_CLOSE_AUDIO);
+ assert(next_song == nullptr);
+
+ idle_add(IDLE_PLAYER);
+}
+
+void
+player_control::UpdateAudio()
+{
+ player_command(this, PLAYER_COMMAND_UPDATE_AUDIO);
+}
+
+void
+player_control::Kill()
+{
+ assert(thread != NULL);
+
+ player_command(this, PLAYER_COMMAND_EXIT);
+ g_thread_join(thread);
+ thread = NULL;
+
+ idle_add(IDLE_PLAYER);
+}
+
+void
+player_control::Pause()
+{
+ Lock();
+
+ if (state != PLAYER_STATE_STOP) {
+ player_command_locked(this, PLAYER_COMMAND_PAUSE);
+ idle_add(IDLE_PLAYER);
+ }
+
+ Unlock();
+}
+
+static void
+pc_pause_locked(struct player_control *pc)
+{
+ if (pc->state != PLAYER_STATE_STOP) {
+ player_command_locked(pc, PLAYER_COMMAND_PAUSE);
+ idle_add(IDLE_PLAYER);
+ }
+}
+
+void
+player_control::SetPause(bool pause_flag)
+{
+ Lock();
+
+ switch (state) {
+ case PLAYER_STATE_STOP:
+ break;
+
+ case PLAYER_STATE_PLAY:
+ if (pause_flag)
+ pc_pause_locked(this);
+ break;
+
+ case PLAYER_STATE_PAUSE:
+ if (!pause_flag)
+ pc_pause_locked(this);
+ break;
+ }
+
+ Unlock();
+}
+
+void
+player_control::SetBorderPause(bool _border_pause)
+{
+ Lock();
+ border_pause = _border_pause;
+ Unlock();
+}
+
+player_status
+player_control::GetStatus()
+{
+ player_status status;
+
+ Lock();
+ player_command_locked(this, PLAYER_COMMAND_REFRESH);
+
+ status.state = state;
+
+ if (state != PLAYER_STATE_STOP) {
+ status.bit_rate = bit_rate;
+ status.audio_format = audio_format;
+ status.total_time = total_time;
+ status.elapsed_time = elapsed_time;
+ }
+
+ Unlock();
+
+ return status;
+}
+
+void
+player_control::SetError(player_error type, GError *_error)
+{
+ assert(type != PLAYER_ERROR_NONE);
+ assert(_error != NULL);
+
+ if (error_type != PLAYER_ERROR_NONE)
+ g_error_free(error);
+
+ error_type = type;
+ error = _error;
+}
+
+void
+player_control::ClearError()
+{
+ Lock();
+
+ if (error_type != PLAYER_ERROR_NONE) {
+ error_type = PLAYER_ERROR_NONE;
+ g_error_free(error);
+ }
+
+ Unlock();
+}
+
+char *
+player_control::GetErrorMessage() const
+{
+ Lock();
+ char *message = error_type != PLAYER_ERROR_NONE
+ ? g_strdup(error->message)
+ : NULL;
+ Unlock();
+ return message;
+}
+
+static void
+pc_enqueue_song_locked(struct player_control *pc, Song *song)
+{
+ assert(song != NULL);
+ assert(pc->next_song == NULL);
+
+ pc->next_song = song;
+ player_command_locked(pc, PLAYER_COMMAND_QUEUE);
+}
+
+void
+player_control::EnqueueSong(Song *song)
+{
+ assert(song != NULL);
+
+ Lock();
+ pc_enqueue_song_locked(this, song);
+ Unlock();
+}
+
+bool
+player_control::Seek(Song *song, float seek_time)
+{
+ assert(song != NULL);
+
+ Lock();
+
+ if (next_song != nullptr)
+ next_song->Free();
+
+ next_song = song;
+ seek_where = seek_time;
+ player_command_locked(this, PLAYER_COMMAND_SEEK);
+ Unlock();
+
+ assert(next_song == nullptr);
+
+ idle_add(IDLE_PLAYER);
+
+ return true;
+}
+
+void
+player_control::SetCrossFade(float _cross_fade_seconds)
+{
+ if (_cross_fade_seconds < 0)
+ _cross_fade_seconds = 0;
+ cross_fade_seconds = _cross_fade_seconds;
+
+ idle_add(IDLE_OPTIONS);
+}
+
+void
+player_control::SetMixRampDb(float _mixramp_db)
+{
+ mixramp_db = _mixramp_db;
+
+ idle_add(IDLE_OPTIONS);
+}
+
+void
+player_control::SetMixRampDelay(float _mixramp_delay_seconds)
+{
+ mixramp_delay_seconds = _mixramp_delay_seconds;
+
+ idle_add(IDLE_OPTIONS);
+}
diff --git a/src/PlayerControl.hxx b/src/PlayerControl.hxx
new file mode 100644
index 000000000..bea2e05eb
--- /dev/null
+++ b/src/PlayerControl.hxx
@@ -0,0 +1,326 @@
+/*
+ * 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_PLAYER_H
+#define MPD_PLAYER_H
+
+#include "AudioFormat.hxx"
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+
+#include <glib.h>
+
+#include <stdint.h>
+
+struct decoder_control;
+struct Song;
+
+enum player_state {
+ PLAYER_STATE_STOP = 0,
+ PLAYER_STATE_PAUSE,
+ PLAYER_STATE_PLAY
+};
+
+enum player_command {
+ PLAYER_COMMAND_NONE = 0,
+ PLAYER_COMMAND_EXIT,
+ PLAYER_COMMAND_STOP,
+ PLAYER_COMMAND_PAUSE,
+ PLAYER_COMMAND_SEEK,
+ PLAYER_COMMAND_CLOSE_AUDIO,
+
+ /**
+ * At least one audio_output.enabled flag has been modified;
+ * commit those changes to the output threads.
+ */
+ PLAYER_COMMAND_UPDATE_AUDIO,
+
+ /** player_control.next_song has been updated */
+ PLAYER_COMMAND_QUEUE,
+
+ /**
+ * cancel pre-decoding player_control.next_song; if the player
+ * has already started playing this song, it will completely
+ * stop
+ */
+ PLAYER_COMMAND_CANCEL,
+
+ /**
+ * Refresh status information in the #player_control struct,
+ * e.g. elapsed_time.
+ */
+ PLAYER_COMMAND_REFRESH,
+};
+
+enum player_error {
+ PLAYER_ERROR_NONE = 0,
+
+ /**
+ * The decoder has failed to decode the song.
+ */
+ PLAYER_ERROR_DECODER,
+
+ /**
+ * The audio output has failed.
+ */
+ PLAYER_ERROR_OUTPUT,
+};
+
+struct player_status {
+ enum player_state state;
+ uint16_t bit_rate;
+ AudioFormat audio_format;
+ float total_time;
+ float elapsed_time;
+};
+
+struct player_control {
+ unsigned buffer_chunks;
+
+ unsigned int buffered_before_play;
+
+ /** the handle of the player thread, or NULL if the player
+ thread isn't running */
+ GThread *thread;
+
+ /**
+ * This lock protects #command, #state, #error.
+ */
+ mutable Mutex mutex;
+
+ /**
+ * Trigger this object after you have modified #command.
+ */
+ Cond cond;
+
+ /**
+ * This object gets signalled when the player thread has
+ * finished the #command. It wakes up the client that waits
+ * (i.e. the main thread).
+ */
+ Cond client_cond;
+
+ enum player_command command;
+ enum player_state state;
+
+ enum player_error error_type;
+
+ /**
+ * The error that occurred in the player thread. This
+ * attribute is only valid if #error is not
+ * #PLAYER_ERROR_NONE. The object must be freed when this
+ * object transitions back to #PLAYER_ERROR_NONE.
+ */
+ GError *error;
+
+ uint16_t bit_rate;
+ AudioFormat audio_format;
+ float total_time;
+ float elapsed_time;
+
+ /**
+ * The next queued song.
+ *
+ * This is a duplicate, and must be freed when this attribute
+ * is cleared.
+ */
+ Song *next_song;
+
+ double seek_where;
+ float cross_fade_seconds;
+ float mixramp_db;
+ float mixramp_delay_seconds;
+ double total_play_time;
+
+ /**
+ * If this flag is set, then the player will be auto-paused at
+ * the end of the song, before the next song starts to play.
+ *
+ * This is a copy of the queue's "single" flag most of the
+ * time.
+ */
+ bool border_pause;
+
+ player_control(unsigned buffer_chunks,
+ unsigned buffered_before_play);
+ ~player_control();
+
+ /**
+ * Locks the object.
+ */
+ void Lock() const {
+ mutex.lock();
+ }
+
+ /**
+ * Unlocks the object.
+ */
+ void Unlock() const {
+ mutex.unlock();
+ }
+
+ /**
+ * Signals the object. The object should be locked prior to
+ * calling this function.
+ */
+ void Signal() {
+ cond.signal();
+ }
+
+ /**
+ * Signals the object. The object is temporarily locked by
+ * this function.
+ */
+ void LockSignal() {
+ Lock();
+ Signal();
+ Unlock();
+ }
+
+ /**
+ * Waits for a signal on the object. This function is only
+ * valid in the player thread. The object must be locked
+ * prior to calling this function.
+ */
+ void Wait() {
+ assert(thread == g_thread_self());
+
+ cond.wait(mutex);
+ }
+
+ /**
+ * Wake up the client waiting for command completion.
+ *
+ * Caller must lock the object.
+ */
+ void ClientSignal() {
+ assert(thread == g_thread_self());
+
+ client_cond.signal();
+ }
+
+ /**
+ * The client calls this method to wait for command
+ * completion.
+ *
+ * Caller must lock the object.
+ */
+ void ClientWait() {
+ assert(thread != g_thread_self());
+
+ client_cond.wait(mutex);
+ }
+
+ /**
+ * @param song the song to be queued; the given instance will
+ * be owned and freed by the player
+ */
+ void Play(Song *song);
+
+ /**
+ * see PLAYER_COMMAND_CANCEL
+ */
+ void Cancel();
+
+ void SetPause(bool pause_flag);
+
+ void Pause();
+
+ /**
+ * Set the player's #border_pause flag.
+ */
+ void SetBorderPause(bool border_pause);
+
+ void Kill();
+
+ gcc_pure
+ player_status GetStatus();
+
+ player_state GetState() const {
+ return state;
+ }
+
+ /**
+ * Set the error. Discards any previous error condition.
+ *
+ * Caller must lock the object.
+ *
+ * @param type the error type; must not be #PLAYER_ERROR_NONE
+ * @param error detailed error information; must not be NULL; the
+ * #player_control takes over ownership of this #GError instance
+ */
+ void SetError(player_error type, GError *error);
+
+ void ClearError();
+
+ /**
+ * Returns the human-readable message describing the last
+ * error during playback, NULL if no error occurred. The
+ * caller has to free the returned string.
+ */
+ char *GetErrorMessage() const;
+
+ player_error GetErrorType() const {
+ return error_type;
+ }
+
+ void Stop();
+
+ void UpdateAudio();
+
+ /**
+ * @param song the song to be queued; the given instance will be owned
+ * and freed by the player
+ */
+ void EnqueueSong(Song *song);
+
+ /**
+ * Makes the player thread seek the specified song to a position.
+ *
+ * @param song the song to be queued; the given instance will be owned
+ * and freed by the player
+ * @return true on success, false on failure (e.g. if MPD isn't
+ * playing currently)
+ */
+ bool Seek(Song *song, float seek_time);
+
+ void SetCrossFade(float cross_fade_seconds);
+
+ float GetCrossFade() const {
+ return cross_fade_seconds;
+ }
+
+ void SetMixRampDb(float mixramp_db);
+
+ float GetMixRampDb() const {
+ return mixramp_db;
+ }
+
+ void SetMixRampDelay(float mixramp_delay_seconds);
+
+ float GetMixRampDelay() const {
+ return mixramp_delay_seconds;
+ }
+
+ double GetTotalPlayTime() const {
+ return total_play_time;
+ }
+};
+
+#endif
diff --git a/src/PlayerThread.cxx b/src/PlayerThread.cxx
new file mode 100644
index 000000000..68fd6390f
--- /dev/null
+++ b/src/PlayerThread.cxx
@@ -0,0 +1,1212 @@
+/*
+ * 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 "PlayerThread.hxx"
+#include "DecoderThread.hxx"
+#include "DecoderControl.hxx"
+#include "MusicPipe.hxx"
+#include "MusicBuffer.hxx"
+#include "MusicChunk.hxx"
+#include "Song.hxx"
+#include "Main.hxx"
+#include "mpd_error.h"
+#include "CrossFade.hxx"
+#include "PlayerControl.hxx"
+#include "OutputAll.hxx"
+#include "Tag.hxx"
+#include "Idle.hxx"
+#include "GlobalEvents.hxx"
+
+#include <cmath>
+
+#include <glib.h>
+
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "player_thread"
+
+enum xfade_state {
+ XFADE_DISABLED = -1,
+ XFADE_UNKNOWN = 0,
+ XFADE_ENABLED = 1
+};
+
+struct player {
+ struct player_control *pc;
+
+ struct decoder_control *dc;
+
+ struct music_pipe *pipe;
+
+ /**
+ * are we waiting for buffered_before_play?
+ */
+ bool buffering;
+
+ /**
+ * true if the decoder is starting and did not provide data
+ * yet
+ */
+ bool decoder_starting;
+
+ /**
+ * is the player paused?
+ */
+ bool paused;
+
+ /**
+ * is there a new song in pc.next_song?
+ */
+ bool queued;
+
+ /**
+ * Was any audio output opened successfully? It might have
+ * failed meanwhile, but was not explicitly closed by the
+ * player thread. When this flag is unset, some output
+ * methods must not be called.
+ */
+ bool output_open;
+
+ /**
+ * the song currently being played
+ */
+ Song *song;
+
+ /**
+ * is cross fading enabled?
+ */
+ enum xfade_state xfade;
+
+ /**
+ * has cross-fading begun?
+ */
+ bool cross_fading;
+
+ /**
+ * The number of chunks used for crossfading.
+ */
+ unsigned cross_fade_chunks;
+
+ /**
+ * The tag of the "next" song during cross-fade. It is
+ * postponed, and sent to the output thread when the new song
+ * really begins.
+ */
+ Tag *cross_fade_tag;
+
+ /**
+ * The current audio format for the audio outputs.
+ */
+ AudioFormat play_audio_format;
+
+ /**
+ * The time stamp of the chunk most recently sent to the
+ * output thread. This attribute is only used if
+ * audio_output_all_get_elapsed_time() didn't return a usable
+ * value; the output thread can estimate the elapsed time more
+ * precisely.
+ */
+ float elapsed_time;
+
+ player(player_control *_pc, decoder_control *_dc)
+ :pc(_pc), dc(_dc),
+ buffering(false),
+ decoder_starting(false),
+ paused(false),
+ queued(true),
+ output_open(false),
+ song(NULL),
+ xfade(XFADE_UNKNOWN),
+ cross_fading(false),
+ cross_fade_chunks(0),
+ cross_fade_tag(NULL),
+ elapsed_time(0.0) {}
+};
+
+static struct music_buffer *player_buffer;
+
+static void
+player_command_finished_locked(struct player_control *pc)
+{
+ assert(pc->command != PLAYER_COMMAND_NONE);
+
+ pc->command = PLAYER_COMMAND_NONE;
+ pc->ClientSignal();
+}
+
+static void
+player_command_finished(struct player_control *pc)
+{
+ pc->Lock();
+ player_command_finished_locked(pc);
+ pc->Unlock();
+}
+
+/**
+ * Start the decoder.
+ *
+ * Player lock is not held.
+ */
+static void
+player_dc_start(struct player *player, struct music_pipe *pipe)
+{
+ struct player_control *pc = player->pc;
+ struct decoder_control *dc = player->dc;
+
+ assert(player->queued || pc->command == PLAYER_COMMAND_SEEK);
+ assert(pc->next_song != NULL);
+
+ unsigned start_ms = pc->next_song->start_ms;
+ if (pc->command == PLAYER_COMMAND_SEEK)
+ start_ms += (unsigned)(pc->seek_where * 1000);
+
+ dc->Start(pc->next_song->DupDetached(),
+ start_ms, pc->next_song->end_ms,
+ player_buffer, pipe);
+}
+
+/**
+ * Is the decoder still busy on the same song as the player?
+ *
+ * Note: this function does not check if the decoder is already
+ * finished.
+ */
+static bool
+player_dc_at_current_song(const struct player *player)
+{
+ assert(player != NULL);
+ assert(player->pipe != NULL);
+
+ return player->dc->pipe == player->pipe;
+}
+
+/**
+ * Returns true if the decoder is decoding the next song (or has begun
+ * decoding it, or has finished doing it), and the player hasn't
+ * switched to that song yet.
+ */
+static bool
+player_dc_at_next_song(const struct player *player)
+{
+ return player->dc->pipe != NULL && !player_dc_at_current_song(player);
+}
+
+/**
+ * Stop the decoder and clears (and frees) its music pipe.
+ *
+ * Player lock is not held.
+ */
+static void
+player_dc_stop(struct player *player)
+{
+ struct decoder_control *dc = player->dc;
+
+ dc->Stop();
+
+ if (dc->pipe != NULL) {
+ /* clear and free the decoder pipe */
+
+ music_pipe_clear(dc->pipe, player_buffer);
+
+ if (dc->pipe != player->pipe)
+ music_pipe_free(dc->pipe);
+
+ dc->pipe = NULL;
+ }
+}
+
+/**
+ * After the decoder has been started asynchronously, wait for the
+ * "START" command to finish. The decoder may not be initialized yet,
+ * i.e. there is no audio_format information yet.
+ *
+ * The player lock is not held.
+ */
+static bool
+player_wait_for_decoder(struct player *player)
+{
+ struct player_control *pc = player->pc;
+ struct decoder_control *dc = player->dc;
+
+ assert(player->queued || pc->command == PLAYER_COMMAND_SEEK);
+ assert(pc->next_song != NULL);
+
+ player->queued = false;
+
+ GError *error = dc->LockGetError();
+ if (error != NULL) {
+ pc->Lock();
+ pc->SetError(PLAYER_ERROR_DECODER, error);
+
+ pc->next_song->Free();
+ pc->next_song = NULL;
+
+ pc->Unlock();
+
+ return false;
+ }
+
+ if (player->song != NULL)
+ player->song->Free();
+
+ player->song = pc->next_song;
+ player->elapsed_time = 0.0;
+
+ /* set the "starting" flag, which will be cleared by
+ player_check_decoder_startup() */
+ player->decoder_starting = true;
+
+ pc->Lock();
+
+ /* update player_control's song information */
+ pc->total_time = pc->next_song->GetDuration();
+ pc->bit_rate = 0;
+ pc->audio_format.Clear();
+
+ /* clear the queued song */
+ pc->next_song = NULL;
+
+ pc->Unlock();
+
+ /* call syncPlaylistWithQueue() in the main thread */
+ GlobalEvents::Emit(GlobalEvents::PLAYLIST);
+
+ return true;
+}
+
+/**
+ * Returns the real duration of the song, comprising the duration
+ * indicated by the decoder plugin.
+ */
+static double
+real_song_duration(const Song *song, double decoder_duration)
+{
+ assert(song != NULL);
+
+ if (decoder_duration <= 0.0)
+ /* the decoder plugin didn't provide information; fall
+ back to 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;
+
+ return decoder_duration - song->start_ms / 1000.0;
+}
+
+/**
+ * Wrapper for audio_output_all_open(). Upon failure, it pauses the
+ * player.
+ *
+ * @return true on success
+ */
+static bool
+player_open_output(struct player *player)
+{
+ struct player_control *pc = player->pc;
+
+ assert(player->play_audio_format.IsDefined());
+ assert(pc->state == PLAYER_STATE_PLAY ||
+ pc->state == PLAYER_STATE_PAUSE);
+
+ GError *error = NULL;
+ if (audio_output_all_open(player->play_audio_format, player_buffer,
+ &error)) {
+ player->output_open = true;
+ player->paused = false;
+
+ pc->Lock();
+ pc->state = PLAYER_STATE_PLAY;
+ pc->Unlock();
+
+ idle_add(IDLE_PLAYER);
+
+ return true;
+ } else {
+ g_warning("%s", error->message);
+
+ player->output_open = false;
+
+ /* pause: the user may resume playback as soon as an
+ audio output becomes available */
+ player->paused = true;
+
+ pc->Lock();
+ pc->SetError(PLAYER_ERROR_OUTPUT, error);
+ pc->state = PLAYER_STATE_PAUSE;
+ pc->Unlock();
+
+ idle_add(IDLE_PLAYER);
+
+ return false;
+ }
+}
+
+/**
+ * The decoder has acknowledged the "START" command (see
+ * player_wait_for_decoder()). This function checks if the decoder
+ * initialization has completed yet.
+ *
+ * The player lock is not held.
+ */
+static bool
+player_check_decoder_startup(struct player *player)
+{
+ struct player_control *pc = player->pc;
+ struct decoder_control *dc = player->dc;
+
+ assert(player->decoder_starting);
+
+ dc->Lock();
+
+ GError *error = dc->GetError();
+ if (error != NULL) {
+ /* the decoder failed */
+ dc->Unlock();
+
+ pc->Lock();
+ pc->SetError(PLAYER_ERROR_DECODER, error);
+ pc->Unlock();
+
+ return false;
+ } else if (!dc->IsStarting()) {
+ /* the decoder is ready and ok */
+
+ dc->Unlock();
+
+ if (player->output_open &&
+ !audio_output_all_wait(pc, 1))
+ /* the output devices havn't finished playing
+ all chunks yet - wait for that */
+ return true;
+
+ pc->Lock();
+ pc->total_time = real_song_duration(dc->song, dc->total_time);
+ pc->audio_format = dc->in_audio_format;
+ pc->Unlock();
+
+ idle_add(IDLE_PLAYER);
+
+ player->play_audio_format = dc->out_audio_format;
+ player->decoder_starting = false;
+
+ if (!player->paused && !player_open_output(player)) {
+ char *uri = dc->song->GetURI();
+ g_warning("problems opening audio device "
+ "while playing \"%s\"", uri);
+ g_free(uri);
+
+ return true;
+ }
+
+ return true;
+ } else {
+ /* the decoder is not yet ready; wait
+ some more */
+ dc->WaitForDecoder();
+ dc->Unlock();
+
+ return true;
+ }
+}
+
+/**
+ * Sends a chunk of silence to the audio outputs. This is called when
+ * there is not enough decoded data in the pipe yet, to prevent
+ * underruns in the hardware buffers.
+ *
+ * The player lock is not held.
+ */
+static bool
+player_send_silence(struct player *player)
+{
+ assert(player->output_open);
+ assert(player->play_audio_format.IsDefined());
+
+ struct music_chunk *chunk = music_buffer_allocate(player_buffer);
+ if (chunk == NULL) {
+ g_warning("Failed to allocate silence buffer");
+ return false;
+ }
+
+#ifndef NDEBUG
+ chunk->audio_format = player->play_audio_format;
+#endif
+
+ const size_t frame_size = player->play_audio_format.GetFrameSize();
+ /* this formula ensures that we don't send
+ partial frames */
+ unsigned num_frames = sizeof(chunk->data) / frame_size;
+
+ chunk->times = -1.0; /* undefined time stamp */
+ chunk->length = num_frames * frame_size;
+ memset(chunk->data, 0, chunk->length);
+
+ GError *error = NULL;
+ if (!audio_output_all_play(chunk, &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+
+ music_buffer_return(player_buffer, chunk);
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * This is the handler for the #PLAYER_COMMAND_SEEK command.
+ *
+ * The player lock is not held.
+ */
+static bool player_seek_decoder(struct player *player)
+{
+ struct player_control *pc = player->pc;
+ Song *song = pc->next_song;
+ struct decoder_control *dc = player->dc;
+
+ assert(pc->next_song != NULL);
+
+ const unsigned start_ms = song->start_ms;
+
+ if (!dc->LockIsCurrentSong(song)) {
+ /* the decoder is already decoding the "next" song -
+ stop it and start the previous song again */
+
+ player_dc_stop(player);
+
+ /* clear music chunks which might still reside in the
+ pipe */
+ music_pipe_clear(player->pipe, player_buffer);
+
+ /* re-start the decoder */
+ player_dc_start(player, player->pipe);
+ if (!player_wait_for_decoder(player)) {
+ /* decoder failure */
+ player_command_finished(pc);
+ return false;
+ }
+ } else {
+ if (!player_dc_at_current_song(player)) {
+ /* the decoder is already decoding the "next" song,
+ but it is the same song file; exchange the pipe */
+ music_pipe_clear(player->pipe, player_buffer);
+ music_pipe_free(player->pipe);
+ player->pipe = dc->pipe;
+ }
+
+ pc->next_song->Free();
+ pc->next_song = NULL;
+ player->queued = false;
+ }
+
+ /* wait for the decoder to complete initialization */
+
+ while (player->decoder_starting) {
+ if (!player_check_decoder_startup(player)) {
+ /* decoder failure */
+ player_command_finished(pc);
+ return false;
+ }
+ }
+
+ /* send the SEEK command */
+
+ double where = pc->seek_where;
+ if (where > pc->total_time)
+ where = pc->total_time - 0.1;
+ if (where < 0.0)
+ where = 0.0;
+
+ if (!dc->Seek(where + start_ms / 1000.0)) {
+ /* decoder failure */
+ player_command_finished(pc);
+ return false;
+ }
+
+ player->elapsed_time = where;
+
+ player_command_finished(pc);
+
+ player->xfade = XFADE_UNKNOWN;
+
+ /* re-fill the buffer after seeking */
+ player->buffering = true;
+
+ audio_output_all_cancel();
+
+ return true;
+}
+
+/**
+ * Player lock must be held before calling.
+ */
+static void player_process_command(struct player *player)
+{
+ struct player_control *pc = player->pc;
+ G_GNUC_UNUSED struct decoder_control *dc = player->dc;
+
+ switch (pc->command) {
+ case PLAYER_COMMAND_NONE:
+ case PLAYER_COMMAND_STOP:
+ case PLAYER_COMMAND_EXIT:
+ case PLAYER_COMMAND_CLOSE_AUDIO:
+ break;
+
+ case PLAYER_COMMAND_UPDATE_AUDIO:
+ pc->Unlock();
+ audio_output_all_enable_disable();
+ pc->Lock();
+ player_command_finished_locked(pc);
+ break;
+
+ case PLAYER_COMMAND_QUEUE:
+ assert(pc->next_song != NULL);
+ assert(!player->queued);
+ assert(!player_dc_at_next_song(player));
+
+ player->queued = true;
+ player_command_finished_locked(pc);
+ break;
+
+ case PLAYER_COMMAND_PAUSE:
+ pc->Unlock();
+
+ player->paused = !player->paused;
+ if (player->paused) {
+ audio_output_all_pause();
+ pc->Lock();
+
+ pc->state = PLAYER_STATE_PAUSE;
+ } else if (!player->play_audio_format.IsDefined()) {
+ /* the decoder hasn't provided an audio format
+ yet - don't open the audio device yet */
+ pc->Lock();
+
+ pc->state = PLAYER_STATE_PLAY;
+ } else {
+ player_open_output(player);
+
+ pc->Lock();
+ }
+
+ player_command_finished_locked(pc);
+ break;
+
+ case PLAYER_COMMAND_SEEK:
+ pc->Unlock();
+ player_seek_decoder(player);
+ pc->Lock();
+ break;
+
+ case PLAYER_COMMAND_CANCEL:
+ if (pc->next_song == NULL) {
+ /* the cancel request arrived too late, we're
+ already playing the queued song... stop
+ everything now */
+ pc->command = PLAYER_COMMAND_STOP;
+ return;
+ }
+
+ if (player_dc_at_next_song(player)) {
+ /* the decoder is already decoding the song -
+ stop it and reset the position */
+ pc->Unlock();
+ player_dc_stop(player);
+ pc->Lock();
+ }
+
+ pc->next_song->Free();
+ pc->next_song = NULL;
+ player->queued = false;
+ player_command_finished_locked(pc);
+ break;
+
+ case PLAYER_COMMAND_REFRESH:
+ if (player->output_open && !player->paused) {
+ pc->Unlock();
+ audio_output_all_check();
+ pc->Lock();
+ }
+
+ pc->elapsed_time = audio_output_all_get_elapsed_time();
+ if (pc->elapsed_time < 0.0)
+ pc->elapsed_time = player->elapsed_time;
+
+ player_command_finished_locked(pc);
+ break;
+ }
+}
+
+static void
+update_song_tag(Song *song, const Tag &new_tag)
+{
+ 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;
+
+ /* the main thread will update the playlist version when he
+ receives this event */
+ GlobalEvents::Emit(GlobalEvents::TAG);
+
+ /* notify all clients that the tag of the current song has
+ changed */
+ idle_add(IDLE_PLAYER);
+}
+
+/**
+ * Plays a #music_chunk object (after applying software volume). If
+ * it contains a (stream) tag, copy it to the current song, so MPD's
+ * playlist reflects the new stream tag.
+ *
+ * Player lock is not held.
+ */
+static bool
+play_chunk(struct player_control *pc,
+ Song *song, struct music_chunk *chunk,
+ const AudioFormat format,
+ GError **error_r)
+{
+ assert(chunk->CheckFormat(format));
+
+ if (chunk->tag != NULL)
+ update_song_tag(song, *chunk->tag);
+
+ if (chunk->length == 0) {
+ music_buffer_return(player_buffer, chunk);
+ return true;
+ }
+
+ pc->Lock();
+ pc->bit_rate = chunk->bit_rate;
+ pc->Unlock();
+
+ /* send the chunk to the audio outputs */
+
+ if (!audio_output_all_play(chunk, error_r))
+ return false;
+
+ pc->total_play_time += (double)chunk->length /
+ format.GetTimeToSize();
+ return true;
+}
+
+/**
+ * Obtains the next chunk from the music pipe, optionally applies
+ * cross-fading, and sends it to all audio outputs.
+ *
+ * @return true on success, false on error (playback will be stopped)
+ */
+static bool
+play_next_chunk(struct player *player)
+{
+ struct player_control *pc = player->pc;
+ struct decoder_control *dc = player->dc;
+
+ if (!audio_output_all_wait(pc, 64))
+ /* the output pipe is still large enough, don't send
+ another chunk */
+ return true;
+
+ unsigned cross_fade_position;
+ struct music_chunk *chunk = NULL;
+ if (player->xfade == XFADE_ENABLED &&
+ player_dc_at_next_song(player) &&
+ (cross_fade_position = music_pipe_size(player->pipe))
+ <= player->cross_fade_chunks) {
+ /* perform cross fade */
+ struct music_chunk *other_chunk =
+ music_pipe_shift(dc->pipe);
+
+ if (!player->cross_fading) {
+ /* beginning of the cross fade - adjust
+ crossFadeChunks which might be bigger than
+ the remaining number of chunks in the old
+ song */
+ player->cross_fade_chunks = cross_fade_position;
+ player->cross_fading = true;
+ }
+
+ if (other_chunk != NULL) {
+ chunk = music_pipe_shift(player->pipe);
+ assert(chunk != NULL);
+ assert(chunk->other == NULL);
+
+ /* don't send the tags of the new song (which
+ is being faded in) yet; postpone it until
+ the current song is faded out */
+ player->cross_fade_tag =
+ Tag::MergeReplace(player->cross_fade_tag,
+ other_chunk->tag);
+ other_chunk->tag = NULL;
+
+ if (std::isnan(pc->mixramp_delay_seconds)) {
+ chunk->mix_ratio = ((float)cross_fade_position)
+ / player->cross_fade_chunks;
+ } else {
+ chunk->mix_ratio = nan("");
+ }
+
+ if (other_chunk->IsEmpty()) {
+ /* the "other" chunk was a music_chunk
+ which had only a tag, but no music
+ data - we cannot cross-fade that;
+ but since this happens only at the
+ beginning of the new song, we can
+ easily recover by throwing it away
+ now */
+ music_buffer_return(player_buffer,
+ other_chunk);
+ other_chunk = NULL;
+ }
+
+ chunk->other = other_chunk;
+ } else {
+ /* there are not enough decoded chunks yet */
+
+ dc->Lock();
+
+ if (dc->IsIdle()) {
+ /* the decoder isn't running, abort
+ cross fading */
+ dc->Unlock();
+
+ player->xfade = XFADE_DISABLED;
+ } else {
+ /* wait for the decoder */
+ dc->Signal();
+ dc->WaitForDecoder();
+ dc->Unlock();
+
+ return true;
+ }
+ }
+ }
+
+ if (chunk == NULL)
+ chunk = music_pipe_shift(player->pipe);
+
+ assert(chunk != NULL);
+
+ /* insert the postponed tag if cross-fading is finished */
+
+ if (player->xfade != XFADE_ENABLED && player->cross_fade_tag != NULL) {
+ chunk->tag = Tag::MergeReplace(chunk->tag,
+ player->cross_fade_tag);
+ player->cross_fade_tag = NULL;
+ }
+
+ /* play the current chunk */
+
+ GError *error = NULL;
+ if (!play_chunk(player->pc, player->song, chunk,
+ player->play_audio_format, &error)) {
+ g_warning("%s", error->message);
+
+ music_buffer_return(player_buffer, chunk);
+
+ pc->Lock();
+
+ pc->SetError(PLAYER_ERROR_OUTPUT, error);
+
+ /* pause: the user may resume playback as soon as an
+ audio output becomes available */
+ pc->state = PLAYER_STATE_PAUSE;
+ player->paused = true;
+
+ pc->Unlock();
+
+ idle_add(IDLE_PLAYER);
+
+ return false;
+ }
+
+ /* this formula should prevent that the decoder gets woken up
+ with each chunk; it is more efficient to make it decode a
+ larger block at a time */
+ dc->Lock();
+ if (!dc->IsIdle() &&
+ music_pipe_size(dc->pipe) <= (pc->buffered_before_play +
+ music_buffer_size(player_buffer) * 3) / 4)
+ dc->Signal();
+ dc->Unlock();
+
+ return true;
+}
+
+/**
+ * This is called at the border between two songs: the audio output
+ * has consumed all chunks of the current song, and we should start
+ * sending chunks from the next one.
+ *
+ * The player lock is not held.
+ *
+ * @return true on success, false on error (playback will be stopped)
+ */
+static bool
+player_song_border(struct player *player)
+{
+ player->xfade = XFADE_UNKNOWN;
+
+ char *uri = player->song->GetURI();
+ g_message("played \"%s\"", uri);
+ g_free(uri);
+
+ music_pipe_free(player->pipe);
+ player->pipe = player->dc->pipe;
+
+ audio_output_all_song_border();
+
+ if (!player_wait_for_decoder(player))
+ return false;
+
+ struct player_control *const pc = player->pc;
+ pc->Lock();
+
+ const bool border_pause = pc->border_pause;
+ if (border_pause) {
+ player->paused = true;
+ pc->state = PLAYER_STATE_PAUSE;
+ }
+
+ pc->Unlock();
+
+ if (border_pause)
+ idle_add(IDLE_PLAYER);
+
+ return true;
+}
+
+/*
+ * The main loop of the player thread, during playback. This is
+ * basically a state machine, which multiplexes data between the
+ * decoder thread and the output threads.
+ */
+static void do_play(struct player_control *pc, struct decoder_control *dc)
+{
+ player player(pc, dc);
+
+ pc->Unlock();
+
+ player.pipe = music_pipe_new();
+
+ player_dc_start(&player, player.pipe);
+ if (!player_wait_for_decoder(&player)) {
+ assert(player.song == NULL);
+
+ player_dc_stop(&player);
+ player_command_finished(pc);
+ music_pipe_free(player.pipe);
+ GlobalEvents::Emit(GlobalEvents::PLAYLIST);
+ pc->Lock();
+ return;
+ }
+
+ pc->Lock();
+ pc->state = PLAYER_STATE_PLAY;
+
+ if (pc->command == PLAYER_COMMAND_SEEK)
+ player.elapsed_time = pc->seek_where;
+
+ player_command_finished_locked(pc);
+
+ while (true) {
+ player_process_command(&player);
+ if (pc->command == PLAYER_COMMAND_STOP ||
+ pc->command == PLAYER_COMMAND_EXIT ||
+ pc->command == PLAYER_COMMAND_CLOSE_AUDIO) {
+ pc->Unlock();
+ audio_output_all_cancel();
+ break;
+ }
+
+ pc->Unlock();
+
+ if (player.buffering) {
+ /* buffering at the start of the song - wait
+ until the buffer is large enough, to
+ prevent stuttering on slow machines */
+
+ if (music_pipe_size(player.pipe) < pc->buffered_before_play &&
+ !dc->LockIsIdle()) {
+ /* not enough decoded buffer space yet */
+
+ if (!player.paused &&
+ player.output_open &&
+ audio_output_all_check() < 4 &&
+ !player_send_silence(&player))
+ break;
+
+ dc->Lock();
+ /* XXX race condition: check decoder again */
+ dc->WaitForDecoder();
+ dc->Unlock();
+ pc->Lock();
+ continue;
+ } else {
+ /* buffering is complete */
+ player.buffering = false;
+ }
+ }
+
+ if (player.decoder_starting) {
+ /* wait until the decoder is initialized completely */
+
+ if (!player_check_decoder_startup(&player))
+ break;
+
+ pc->Lock();
+ continue;
+ }
+
+#ifndef NDEBUG
+ /*
+ music_pipe_check_format(&play_audio_format,
+ player.next_song_chunk,
+ &dc->out_audio_format);
+ */
+#endif
+
+ if (dc->LockIsIdle() && player.queued &&
+ dc->pipe == player.pipe) {
+ /* the decoder has finished the current song;
+ make it decode the next song */
+
+ assert(dc->pipe == NULL || dc->pipe == player.pipe);
+
+ player_dc_start(&player, music_pipe_new());
+ }
+
+ if (/* no cross-fading if MPD is going to pause at the
+ end of the current song */
+ !pc->border_pause &&
+ player_dc_at_next_song(&player) &&
+ player.xfade == XFADE_UNKNOWN &&
+ !dc->LockIsStarting()) {
+ /* enable cross fading in this song? if yes,
+ calculate how many chunks will be required
+ for it */
+ player.cross_fade_chunks =
+ cross_fade_calc(pc->cross_fade_seconds, dc->total_time,
+ pc->mixramp_db,
+ pc->mixramp_delay_seconds,
+ dc->replay_gain_db,
+ dc->replay_gain_prev_db,
+ dc->mixramp_start,
+ dc->mixramp_prev_end,
+ dc->out_audio_format,
+ player.play_audio_format,
+ music_buffer_size(player_buffer) -
+ pc->buffered_before_play);
+ if (player.cross_fade_chunks > 0) {
+ player.xfade = XFADE_ENABLED;
+ player.cross_fading = false;
+ } else
+ /* cross fading is disabled or the
+ next song is too short */
+ player.xfade = XFADE_DISABLED;
+ }
+
+ if (player.paused) {
+ pc->Lock();
+
+ if (pc->command == PLAYER_COMMAND_NONE)
+ pc->Wait();
+ continue;
+ } else if (!music_pipe_empty(player.pipe)) {
+ /* at least one music chunk is ready - send it
+ to the audio output */
+
+ play_next_chunk(&player);
+ } else if (audio_output_all_check() > 0) {
+ /* not enough data from decoder, but the
+ output thread is still busy, so it's
+ okay */
+
+ /* XXX synchronize in a better way */
+ g_usleep(10000);
+ } else if (player_dc_at_next_song(&player)) {
+ /* at the beginning of a new song */
+
+ if (!player_song_border(&player))
+ break;
+ } else if (dc->LockIsIdle()) {
+ /* check the size of the pipe again, because
+ the decoder thread may have added something
+ since we last checked */
+ if (music_pipe_empty(player.pipe)) {
+ /* wait for the hardware to finish
+ playback */
+ audio_output_all_drain();
+ break;
+ }
+ } else if (player.output_open) {
+ /* the decoder is too busy and hasn't provided
+ new PCM data in time: send silence (if the
+ output pipe is empty) */
+ if (!player_send_silence(&player))
+ break;
+ }
+
+ pc->Lock();
+ }
+
+ player_dc_stop(&player);
+
+ music_pipe_clear(player.pipe, player_buffer);
+ music_pipe_free(player.pipe);
+
+ delete player.cross_fade_tag;
+
+ if (player.song != NULL)
+ player.song->Free();
+
+ pc->Lock();
+
+ if (player.queued) {
+ assert(pc->next_song != NULL);
+ pc->next_song->Free();
+ pc->next_song = NULL;
+ }
+
+ pc->state = PLAYER_STATE_STOP;
+
+ pc->Unlock();
+
+ GlobalEvents::Emit(GlobalEvents::PLAYLIST);
+
+ pc->Lock();
+}
+
+static gpointer
+player_task(gpointer arg)
+{
+ struct player_control *pc = (struct player_control *)arg;
+
+ struct decoder_control *dc = new decoder_control();
+ decoder_thread_start(dc);
+
+ player_buffer = music_buffer_new(pc->buffer_chunks);
+
+ pc->Lock();
+
+ while (1) {
+ switch (pc->command) {
+ case PLAYER_COMMAND_SEEK:
+ case PLAYER_COMMAND_QUEUE:
+ assert(pc->next_song != NULL);
+
+ do_play(pc, dc);
+ break;
+
+ case PLAYER_COMMAND_STOP:
+ pc->Unlock();
+ audio_output_all_cancel();
+ pc->Lock();
+
+ /* fall through */
+
+ case PLAYER_COMMAND_PAUSE:
+ if (pc->next_song != NULL) {
+ pc->next_song->Free();
+ pc->next_song = NULL;
+ }
+
+ player_command_finished_locked(pc);
+ break;
+
+ case PLAYER_COMMAND_CLOSE_AUDIO:
+ pc->Unlock();
+
+ audio_output_all_release();
+
+ pc->Lock();
+ player_command_finished_locked(pc);
+
+#ifndef NDEBUG
+ /* in the DEBUG build, check for leaked
+ music_chunk objects by freeing the
+ music_buffer */
+ music_buffer_free(player_buffer);
+ player_buffer = music_buffer_new(pc->buffer_chunks);
+#endif
+
+ break;
+
+ case PLAYER_COMMAND_UPDATE_AUDIO:
+ pc->Unlock();
+ audio_output_all_enable_disable();
+ pc->Lock();
+ player_command_finished_locked(pc);
+ break;
+
+ case PLAYER_COMMAND_EXIT:
+ pc->Unlock();
+
+ dc->Quit();
+ delete dc;
+ audio_output_all_close();
+ music_buffer_free(player_buffer);
+
+ player_command_finished(pc);
+ return NULL;
+
+ case PLAYER_COMMAND_CANCEL:
+ if (pc->next_song != NULL) {
+ pc->next_song->Free();
+ pc->next_song = NULL;
+ }
+
+ player_command_finished_locked(pc);
+ break;
+
+ case PLAYER_COMMAND_REFRESH:
+ /* no-op when not playing */
+ player_command_finished_locked(pc);
+ break;
+
+ case PLAYER_COMMAND_NONE:
+ pc->Wait();
+ break;
+ }
+ }
+}
+
+void
+player_create(struct player_control *pc)
+{
+ assert(pc->thread == NULL);
+
+#if GLIB_CHECK_VERSION(2,32,0)
+ pc->thread = g_thread_new("player", player_task, pc);
+#else
+ GError *e = NULL;
+ pc->thread = g_thread_create(player_task, pc, true, &e);
+ if (pc->thread == NULL)
+ MPD_ERROR("Failed to spawn player task: %s", e->message);
+#endif
+}
diff --git a/src/PlayerThread.hxx b/src/PlayerThread.hxx
new file mode 100644
index 000000000..197669cd6
--- /dev/null
+++ b/src/PlayerThread.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.
+ */
+
+/* \file
+ *
+ * The player thread controls the playback. It acts as a bridge
+ * between the decoder thread and the output thread(s): it receives
+ * #music_chunk objects from the decoder, optionally mixes them
+ * (cross-fading), applies software volume, and sends them to the
+ * audio outputs via audio_output_all_play().
+ *
+ * It is controlled by the main thread (the playlist code), see
+ * player_control.h. The playlist enqueues new songs into the player
+ * thread and sends it commands.
+ *
+ * The player thread itself does not do any I/O. It synchronizes with
+ * other threads via #GMutex and #GCond objects, and passes
+ * #music_chunk instances around in #music_pipe objects.
+ */
+
+#ifndef MPD_PLAYER_THREAD_HXX
+#define MPD_PLAYER_THREAD_HXX
+
+struct player_control;
+
+void
+player_create(struct player_control *pc);
+
+#endif
diff --git a/src/Playlist.cxx b/src/Playlist.cxx
new file mode 100644
index 000000000..0fc12f745
--- /dev/null
+++ b/src/Playlist.cxx
@@ -0,0 +1,343 @@
+/*
+ * 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 "Playlist.hxx"
+#include "PlayerControl.hxx"
+#include "Song.hxx"
+#include "Idle.hxx"
+
+#include <glib.h>
+
+#include <assert.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "playlist"
+
+void
+playlist::FullIncrementVersions()
+{
+ queue.ModifyAll();
+ idle_add(IDLE_PLAYLIST);
+}
+
+void
+playlist::TagChanged()
+{
+ if (!playing)
+ return;
+
+ assert(current >= 0);
+
+ queue.ModifyAtOrder(current);
+ idle_add(IDLE_PLAYLIST);
+}
+
+/**
+ * Queue a song, addressed by its order number.
+ */
+static void
+playlist_queue_song_order(struct playlist *playlist, struct player_control *pc,
+ unsigned order)
+{
+ char *uri;
+
+ assert(playlist->queue.IsValidOrder(order));
+
+ playlist->queued = order;
+
+ Song *song = playlist->queue.GetOrder(order)->DupDetached();
+
+ uri = song->GetURI();
+ g_debug("queue song %i:\"%s\"", playlist->queued, uri);
+ g_free(uri);
+
+ pc->EnqueueSong(song);
+}
+
+/**
+ * Called if the player thread has started playing the "queued" song.
+ */
+static void
+playlist_song_started(struct playlist *playlist, struct player_control *pc)
+{
+ assert(pc->next_song == NULL);
+ assert(playlist->queued >= -1);
+
+ /* queued song has started: copy queued to current,
+ and notify the clients */
+
+ int current = playlist->current;
+ playlist->current = playlist->queued;
+ playlist->queued = -1;
+
+ if(playlist->queue.consume)
+ playlist->DeleteOrder(*pc, current);
+
+ idle_add(IDLE_PLAYER);
+}
+
+const Song *
+playlist::GetQueuedSong() const
+{
+ return playing && queued >= 0
+ ? queue.GetOrder(queued)
+ : nullptr;
+}
+
+void
+playlist::UpdateQueuedSong(player_control &pc, const Song *prev)
+{
+ if (!playing)
+ return;
+
+ assert(!queue.IsEmpty());
+ assert((queued < 0) == (prev == NULL));
+
+ const int next_order = current >= 0
+ ? queue.GetNextOrder(current)
+ : 0;
+
+ if (next_order == 0 && queue.random && !queue.single) {
+ /* shuffle the song order again, so we get a different
+ order each time the playlist is played
+ completely */
+ const unsigned current_position =
+ queue.OrderToPosition(current);
+
+ queue.ShuffleOrder();
+
+ /* make sure that the current still points to
+ the current song, after the song order has been
+ shuffled */
+ current = queue.PositionToOrder(current_position);
+ }
+
+ const Song *const next_song = next_order >= 0
+ ? queue.GetOrder(next_order)
+ : nullptr;
+
+ if (prev != NULL && next_song != prev) {
+ /* clear the currently queued song */
+ pc.Cancel();
+ queued = -1;
+ }
+
+ if (next_order >= 0) {
+ if (next_song != prev)
+ playlist_queue_song_order(this, &pc, next_order);
+ else
+ queued = next_order;
+ }
+}
+
+void
+playlist::PlayOrder(player_control &pc, int order)
+{
+ playing = true;
+ queued = -1;
+
+ Song *song = queue.GetOrder(order)->DupDetached();
+
+ char *uri = song->GetURI();
+ g_debug("play %i:\"%s\"", order, uri);
+ g_free(uri);
+
+ pc.Play(song);
+ current = order;
+}
+
+static void
+playlist_resume_playback(struct playlist *playlist, struct player_control *pc);
+
+void
+playlist::SyncWithPlayer(player_control &pc)
+{
+ if (!playing)
+ /* this event has reached us out of sync: we aren't
+ playing anymore; ignore the event */
+ return;
+
+ pc.Lock();
+ const player_state pc_state = pc.GetState();
+ const Song *pc_next_song = pc.next_song;
+ pc.Unlock();
+
+ if (pc_state == PLAYER_STATE_STOP)
+ /* the player thread has stopped: check if playback
+ should be restarted with the next song. That can
+ happen if the playlist isn't filling the queue fast
+ enough */
+ playlist_resume_playback(this, &pc);
+ else {
+ /* check if the player thread has already started
+ playing the queued song */
+ if (pc_next_song == nullptr && queued != -1)
+ playlist_song_started(this, &pc);
+
+ pc.Lock();
+ pc_next_song = pc.next_song;
+ pc.Unlock();
+
+ /* make sure the queued song is always set (if
+ possible) */
+ if (pc_next_song == nullptr && queued < 0)
+ UpdateQueuedSong(pc, nullptr);
+ }
+}
+
+/**
+ * The player has stopped for some reason. Check the error, and
+ * decide whether to re-start playback
+ */
+static void
+playlist_resume_playback(struct playlist *playlist, struct player_control *pc)
+{
+ assert(playlist->playing);
+ assert(pc->GetState() == PLAYER_STATE_STOP);
+
+ const auto error = pc->GetErrorType();
+ if (error == PLAYER_ERROR_NONE)
+ playlist->error_count = 0;
+ else
+ ++playlist->error_count;
+
+ if ((playlist->stop_on_error && error != PLAYER_ERROR_NONE) ||
+ error == PLAYER_ERROR_OUTPUT ||
+ playlist->error_count >= playlist->queue.GetLength())
+ /* too many errors, or critical error: stop
+ playback */
+ playlist->Stop(*pc);
+ else
+ /* continue playback at the next song */
+ playlist->PlayNext(*pc);
+}
+
+void
+playlist::SetRepeat(player_control &pc, bool status)
+{
+ if (status == queue.repeat)
+ return;
+
+ queue.repeat = status;
+
+ pc.SetBorderPause(queue.single && !queue.repeat);
+
+ /* if the last song is currently being played, the "next song"
+ might change when repeat mode is toggled */
+ UpdateQueuedSong(pc, GetQueuedSong());
+
+ idle_add(IDLE_OPTIONS);
+}
+
+static void
+playlist_order(struct playlist *playlist)
+{
+ if (playlist->current >= 0)
+ /* update playlist.current, order==position now */
+ playlist->current = playlist->queue.OrderToPosition(playlist->current);
+
+ playlist->queue.RestoreOrder();
+}
+
+void
+playlist::SetSingle(player_control &pc, bool status)
+{
+ if (status == queue.single)
+ return;
+
+ queue.single = status;
+
+ pc.SetBorderPause(queue.single && !queue.repeat);
+
+ /* if the last song is currently being played, the "next song"
+ might change when single mode is toggled */
+ UpdateQueuedSong(pc, GetQueuedSong());
+
+ idle_add(IDLE_OPTIONS);
+}
+
+void
+playlist::SetConsume(bool status)
+{
+ if (status == queue.consume)
+ return;
+
+ queue.consume = status;
+ idle_add(IDLE_OPTIONS);
+}
+
+void
+playlist::SetRandom(player_control &pc, bool status)
+{
+ if (status == queue.random)
+ return;
+
+ const Song *const queued_song = GetQueuedSong();
+
+ queue.random = status;
+
+ if (queue.random) {
+ /* shuffle the queue order, but preserve current */
+
+ const int current_position = GetCurrentPosition();
+
+ queue.ShuffleOrder();
+
+ if (current_position >= 0) {
+ /* make sure the current song is the first in
+ the order list, so the whole rest of the
+ playlist is played after that */
+ unsigned current_order =
+ queue.PositionToOrder(current_position);
+ queue.SwapOrders(0, current_order);
+ current = 0;
+ } else
+ current = -1;
+ } else
+ playlist_order(this);
+
+ UpdateQueuedSong(pc, queued_song);
+
+ idle_add(IDLE_OPTIONS);
+}
+
+int
+playlist::GetCurrentPosition() const
+{
+ return current >= 0
+ ? queue.OrderToPosition(current)
+ : -1;
+}
+
+int
+playlist::GetNextPosition() const
+{
+ if (current < 0)
+ return -1;
+
+ if (queue.single && queue.repeat)
+ return queue.OrderToPosition(current);
+ else if (queue.IsValidOrder(current + 1))
+ return queue.OrderToPosition(current + 1);
+ else if (queue.repeat)
+ return queue.OrderToPosition(0);
+
+ return -1;
+}
diff --git a/src/Playlist.hxx b/src/Playlist.hxx
new file mode 100644
index 000000000..a4594368e
--- /dev/null
+++ b/src/Playlist.hxx
@@ -0,0 +1,255 @@
+/*
+ * 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_PLAYLIST_HXX
+#define MPD_PLAYLIST_HXX
+
+#include "Queue.hxx"
+#include "playlist_error.h"
+
+struct player_control;
+struct Song;
+
+struct playlist {
+ /**
+ * The song queue - it contains the "real" playlist.
+ */
+ struct queue queue;
+
+ /**
+ * This value is true if the player is currently playing (or
+ * should be playing).
+ */
+ bool playing;
+
+ /**
+ * If true, then any error is fatal; if false, MPD will
+ * attempt to play the next song on non-fatal errors. During
+ * seeking, this flag is set.
+ */
+ bool stop_on_error;
+
+ /**
+ * Number of errors since playback was started. If this
+ * number exceeds the length of the playlist, MPD gives up,
+ * because all songs have been tried.
+ */
+ unsigned error_count;
+
+ /**
+ * The "current song pointer". This is the song which is
+ * played when we get the "play" command. It is also the song
+ * which is currently being played.
+ */
+ int current;
+
+ /**
+ * The "next" song to be played, when the current one
+ * finishes. The decoder thread may start decoding and
+ * buffering it, while the "current" song is still playing.
+ *
+ * This variable is only valid if #playing is true.
+ */
+ int queued;
+
+ playlist(unsigned max_length)
+ :queue(max_length), playing(false), current(-1), queued(-1) {
+ }
+
+ ~playlist() {
+ }
+
+ uint32_t GetVersion() const {
+ return queue.version;
+ }
+
+ unsigned GetLength() const {
+ return queue.GetLength();
+ }
+
+ unsigned PositionToId(unsigned position) const {
+ return queue.PositionToId(position);
+ }
+
+ gcc_pure
+ int GetCurrentPosition() const;
+
+ gcc_pure
+ int GetNextPosition() const;
+
+ /**
+ * Returns the song object which is currently queued. Returns
+ * none if there is none (yet?) or if MPD isn't playing.
+ */
+ gcc_pure
+ const Song *GetQueuedSong() const;
+
+ /**
+ * This is the "PLAYLIST" event handler. It is invoked by the
+ * player thread whenever it requests a new queued song, or
+ * when it exits.
+ */
+ void SyncWithPlayer(player_control &pc);
+
+protected:
+ /**
+ * Called by all editing methods after a modification.
+ * Updates the queue version and emits #IDLE_PLAYLIST.
+ */
+ void OnModified();
+
+ /**
+ * Updates the "queued song". Calculates the next song
+ * according to the current one (if MPD isn't playing, it
+ * takes the first song), and queues this song. Clears the
+ * old queued song if there was one.
+ *
+ * @param prev the song which was previously queued, as
+ * determined by playlist_get_queued_song()
+ */
+ void UpdateQueuedSong(player_control &pc, const Song *prev);
+
+public:
+ void Clear(player_control &pc);
+
+ void TagChanged();
+
+ void FullIncrementVersions();
+
+ enum playlist_result AppendSong(player_control &pc,
+ Song *song,
+ unsigned *added_id=nullptr);
+
+ /**
+ * Appends a local file (outside the music database) to the
+ * playlist.
+ *
+ * Note: the caller is responsible for checking permissions.
+ */
+ enum playlist_result AppendFile(player_control &pc,
+ const char *path_utf8,
+ unsigned *added_id=nullptr);
+
+ enum playlist_result AppendURI(player_control &pc,
+ const char *uri_utf8,
+ unsigned *added_id=nullptr);
+
+protected:
+ void DeleteInternal(player_control &pc,
+ unsigned song, const Song **queued_p);
+
+public:
+ enum playlist_result DeletePosition(player_control &pc,
+ unsigned position);
+
+ enum playlist_result DeleteOrder(player_control &pc,
+ unsigned order) {
+ return DeletePosition(pc, queue.OrderToPosition(order));
+ }
+
+ enum playlist_result DeleteId(player_control &pc, unsigned id);
+
+ /**
+ * Deletes a range of songs from the playlist.
+ *
+ * @param start the position of the first song to delete
+ * @param end the position after the last song to delete
+ */
+ enum playlist_result DeleteRange(player_control &pc,
+ unsigned start, unsigned end);
+
+ void DeleteSong(player_control &pc, const Song &song);
+
+ void Shuffle(player_control &pc, unsigned start, unsigned end);
+
+ enum playlist_result MoveRange(player_control &pc,
+ unsigned start, unsigned end, int to);
+
+ enum playlist_result MoveId(player_control &pc, unsigned id, int to);
+
+ enum playlist_result SwapPositions(player_control &pc,
+ unsigned song1, unsigned song2);
+
+ enum playlist_result SwapIds(player_control &pc,
+ unsigned id1, unsigned id2);
+
+ enum playlist_result SetPriorityRange(player_control &pc,
+ unsigned start_position,
+ unsigned end_position,
+ uint8_t priority);
+
+ enum playlist_result SetPriorityId(player_control &pc,
+ unsigned song_id, uint8_t priority);
+
+ void Stop(player_control &pc);
+
+ enum playlist_result PlayPosition(player_control &pc, int position);
+
+ void PlayOrder(player_control &pc, int order);
+
+ enum playlist_result PlayId(player_control &pc, int id);
+
+ void PlayNext(player_control &pc);
+
+ void PlayPrevious(player_control &pc);
+
+ enum playlist_result SeekSongPosition(player_control &pc,
+ unsigned song_position,
+ float seek_time);
+
+ enum playlist_result SeekSongId(player_control &pc,
+ unsigned song_id, float seek_time);
+
+ /**
+ * Seek within the current song. Fails if MPD is not currently
+ * playing.
+ *
+ * @param time the time in seconds
+ * @param relative if true, then the specified time is relative to the
+ * current position
+ */
+ enum playlist_result SeekCurrent(player_control &pc,
+ float seek_time, bool relative);
+
+ bool GetRepeat() const {
+ return queue.repeat;
+ }
+
+ void SetRepeat(player_control &pc, bool new_value);
+
+ bool GetRandom() const {
+ return queue.random;
+ }
+
+ void SetRandom(player_control &pc, bool new_value);
+
+ bool GetSingle() const {
+ return queue.single;
+ }
+
+ void SetSingle(player_control &pc, bool new_value);
+
+ bool GetConsume() const {
+ return queue.consume;
+ }
+
+ void SetConsume(bool new_value);
+};
+
+#endif
diff --git a/src/PlaylistAny.cxx b/src/PlaylistAny.cxx
new file mode 100644
index 000000000..a34333df8
--- /dev/null
+++ b/src/PlaylistAny.cxx
@@ -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.
+ */
+
+#include "config.h"
+#include "PlaylistAny.hxx"
+#include "PlaylistMapper.hxx"
+#include "PlaylistRegistry.hxx"
+#include "util/UriUtil.hxx"
+#include "input_stream.h"
+
+#include <assert.h>
+
+static struct playlist_provider *
+playlist_open_remote(const char *uri, Mutex &mutex, Cond &cond,
+ struct input_stream **is_r)
+{
+ assert(uri_has_scheme(uri));
+
+ struct playlist_provider *playlist =
+ playlist_list_open_uri(uri, mutex, cond);
+ if (playlist != NULL) {
+ *is_r = NULL;
+ return playlist;
+ }
+
+ GError *error = NULL;
+ struct input_stream *is = input_stream_open(uri, mutex, cond, &error);
+ if (is == NULL) {
+ if (error != NULL) {
+ g_warning("Failed to open %s: %s",
+ uri, error->message);
+ g_error_free(error);
+ }
+
+ return NULL;
+ }
+
+ playlist = playlist_list_open_stream(is, uri);
+ if (playlist == NULL) {
+ input_stream_close(is);
+ return NULL;
+ }
+
+ *is_r = is;
+ return playlist;
+}
+
+struct playlist_provider *
+playlist_open_any(const char *uri, Mutex &mutex, Cond &cond,
+ struct input_stream **is_r)
+{
+ return uri_has_scheme(uri)
+ ? playlist_open_remote(uri, mutex, cond, is_r)
+ : playlist_mapper_open(uri, mutex, cond, is_r);
+}
diff --git a/src/PlaylistAny.hxx b/src/PlaylistAny.hxx
new file mode 100644
index 000000000..d69087b3f
--- /dev/null
+++ b/src/PlaylistAny.hxx
@@ -0,0 +1,42 @@
+/*
+ * 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_PLAYLIST_ANY_HXX
+#define MPD_PLAYLIST_ANY_HXX
+
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+
+struct playlist_provider;
+struct input_stream;
+
+/**
+ * Opens a playlist from the specified URI, which can be either an
+ * absolute remote URI (with a scheme) or a relative path to the
+ * music orplaylist directory.
+ *
+ * @param is_r on success, an input_stream object may be returned
+ * here, which must be closed after the playlist_provider object is
+ * freed
+ */
+struct playlist_provider *
+playlist_open_any(const char *uri, Mutex &mutex, Cond &cond,
+ struct input_stream **is_r);
+
+#endif
diff --git a/src/PlaylistCommands.cxx b/src/PlaylistCommands.cxx
new file mode 100644
index 000000000..d68a30ba7
--- /dev/null
+++ b/src/PlaylistCommands.cxx
@@ -0,0 +1,221 @@
+/*
+ * 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 "PlaylistCommands.hxx"
+#include "DatabasePlaylist.hxx"
+#include "CommandError.hxx"
+#include "PlaylistPrint.hxx"
+#include "PlaylistSave.hxx"
+#include "PlaylistFile.hxx"
+#include "PlaylistVector.hxx"
+#include "PlaylistQueue.hxx"
+#include "TimePrint.hxx"
+#include "ClientInternal.hxx"
+#include "protocol/ArgParser.hxx"
+#include "protocol/Result.hxx"
+#include "ls.hxx"
+#include "Playlist.hxx"
+#include "util/UriUtil.hxx"
+
+#include <assert.h>
+#include <stdlib.h>
+
+static void
+print_spl_list(Client *client, const PlaylistVector &list)
+{
+ for (const auto &i : list) {
+ client_printf(client, "playlist: %s\n", i.name.c_str());
+
+ if (i.mtime > 0)
+ time_print(client, "Last-Modified", i.mtime);
+ }
+}
+
+enum command_return
+handle_save(Client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ enum playlist_result result;
+
+ result = spl_save_playlist(argv[1], &client->playlist);
+ return print_playlist_result(client, result);
+}
+
+enum command_return
+handle_load(Client *client, int argc, char *argv[])
+{
+ unsigned start_index, end_index;
+
+ if (argc < 3) {
+ start_index = 0;
+ end_index = G_MAXUINT;
+ } else if (!check_range(client, &start_index, &end_index, argv[2]))
+ return COMMAND_RETURN_ERROR;
+
+ enum playlist_result result;
+
+ result = playlist_open_into_queue(argv[1],
+ start_index, end_index,
+ &client->playlist,
+ client->player_control, true);
+ if (result != PLAYLIST_RESULT_NO_SUCH_LIST)
+ return print_playlist_result(client, result);
+
+ GError *error = NULL;
+ if (playlist_load_spl(&client->playlist, client->player_control,
+ argv[1], start_index, end_index,
+ &error))
+ return COMMAND_RETURN_OK;
+
+ if (error->domain == playlist_quark() &&
+ error->code == PLAYLIST_RESULT_BAD_NAME)
+ /* the message for BAD_NAME is confusing when the
+ client wants to load a playlist file from the music
+ directory; patch the GError object to show "no such
+ playlist" instead */
+ error->code = PLAYLIST_RESULT_NO_SUCH_LIST;
+
+ return print_error(client, error);
+}
+
+enum command_return
+handle_listplaylist(Client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ if (playlist_file_print(client, argv[1], false))
+ return COMMAND_RETURN_OK;
+
+ GError *error = NULL;
+ return spl_print(client, argv[1], false, &error)
+ ? COMMAND_RETURN_OK
+ : print_error(client, error);
+}
+
+enum command_return
+handle_listplaylistinfo(Client *client,
+ G_GNUC_UNUSED int argc, char *argv[])
+{
+ if (playlist_file_print(client, argv[1], true))
+ return COMMAND_RETURN_OK;
+
+ GError *error = NULL;
+ return spl_print(client, argv[1], true, &error)
+ ? COMMAND_RETURN_OK
+ : print_error(client, error);
+}
+
+enum command_return
+handle_rm(Client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ GError *error = NULL;
+ return spl_delete(argv[1], &error)
+ ? COMMAND_RETURN_OK
+ : print_error(client, error);
+}
+
+enum command_return
+handle_rename(Client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ GError *error = NULL;
+ return spl_rename(argv[1], argv[2], &error)
+ ? COMMAND_RETURN_OK
+ : print_error(client, error);
+}
+
+enum command_return
+handle_playlistdelete(Client *client,
+ G_GNUC_UNUSED int argc, char *argv[]) {
+ char *playlist = argv[1];
+ unsigned from;
+
+ if (!check_unsigned(client, &from, argv[2]))
+ return COMMAND_RETURN_ERROR;
+
+ GError *error = NULL;
+ return spl_remove_index(playlist, from, &error)
+ ? COMMAND_RETURN_OK
+ : print_error(client, error);
+}
+
+enum command_return
+handle_playlistmove(Client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ char *playlist = argv[1];
+ unsigned from, to;
+
+ if (!check_unsigned(client, &from, argv[2]))
+ return COMMAND_RETURN_ERROR;
+ if (!check_unsigned(client, &to, argv[3]))
+ return COMMAND_RETURN_ERROR;
+
+ GError *error = NULL;
+ return spl_move_index(playlist, from, to, &error)
+ ? COMMAND_RETURN_OK
+ : print_error(client, error);
+}
+
+enum command_return
+handle_playlistclear(Client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ GError *error = NULL;
+ return spl_clear(argv[1], &error)
+ ? COMMAND_RETURN_OK
+ : print_error(client, error);
+}
+
+enum command_return
+handle_playlistadd(Client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ char *playlist = argv[1];
+ char *uri = argv[2];
+
+ bool success;
+ GError *error = NULL;
+ if (uri_has_scheme(uri)) {
+ if (!uri_supported_scheme(uri)) {
+ command_error(client, ACK_ERROR_NO_EXIST,
+ "unsupported URI scheme");
+ return COMMAND_RETURN_ERROR;
+ }
+
+ success = spl_append_uri(uri, playlist, &error);
+ } else
+ success = search_add_to_playlist(uri, playlist, nullptr,
+ &error);
+
+ if (!success && error == NULL) {
+ command_error(client, ACK_ERROR_NO_EXIST,
+ "directory or file not found");
+ return COMMAND_RETURN_ERROR;
+ }
+
+ return success ? COMMAND_RETURN_OK : print_error(client, error);
+}
+
+enum command_return
+handle_listplaylists(Client *client,
+ G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
+{
+ GError *error = NULL;
+ const auto list = ListPlaylistFiles(&error);
+ if (list.empty() && error != NULL)
+ return print_error(client, error);
+
+ print_spl_list(client, list);
+ return COMMAND_RETURN_OK;
+}
diff --git a/src/PlaylistCommands.hxx b/src/PlaylistCommands.hxx
new file mode 100644
index 000000000..067f428b6
--- /dev/null
+++ b/src/PlaylistCommands.hxx
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ */
+
+#ifndef MPD_PLAYLIST_COMMANDS_HXX
+#define MPD_PLAYLIST_COMMANDS_HXX
+
+#include "command.h"
+
+class Client;
+
+enum command_return
+handle_save(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_load(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_listplaylist(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_listplaylistinfo(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_rm(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_rename(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_playlistdelete(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_playlistmove(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_playlistclear(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_playlistadd(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_listplaylists(Client *client, int argc, char *argv[]);
+
+#endif
diff --git a/src/PlaylistControl.cxx b/src/PlaylistControl.cxx
new file mode 100644
index 000000000..5b33486e3
--- /dev/null
+++ b/src/PlaylistControl.cxx
@@ -0,0 +1,264 @@
+/*
+ * 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 controlling playback on the playlist level.
+ *
+ */
+
+#include "config.h"
+#include "Playlist.hxx"
+#include "PlayerControl.hxx"
+#include "Song.hxx"
+
+#include <glib.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "playlist"
+
+void
+playlist::Stop(player_control &pc)
+{
+ if (!playing)
+ return;
+
+ assert(current >= 0);
+
+ g_debug("stop");
+ pc.Stop();
+ queued = -1;
+ playing = false;
+
+ if (queue.random) {
+ /* shuffle the playlist, so the next playback will
+ result in a new random order */
+
+ unsigned current_position = queue.OrderToPosition(current);
+
+ queue.ShuffleOrder();
+
+ /* make sure that "current" stays valid, and the next
+ "play" command plays the same song again */
+ current = queue.PositionToOrder(current_position);
+ }
+}
+
+enum playlist_result
+playlist::PlayPosition(player_control &pc, int song)
+{
+ pc.ClearError();
+
+ unsigned i = song;
+ if (song == -1) {
+ /* play any song ("current" song, or the first song */
+
+ if (queue.IsEmpty())
+ return PLAYLIST_RESULT_SUCCESS;
+
+ if (playing) {
+ /* already playing: unpause playback, just in
+ case it was paused, and return */
+ pc.SetPause(false);
+ return PLAYLIST_RESULT_SUCCESS;
+ }
+
+ /* select a song: "current" song, or the first one */
+ i = current >= 0
+ ? current
+ : 0;
+ } else if (!queue.IsValidPosition(song))
+ return PLAYLIST_RESULT_BAD_RANGE;
+
+ if (queue.random) {
+ if (song >= 0)
+ /* "i" is currently the song position (which
+ would be equal to the order number in
+ no-random mode); convert it to a order
+ number, because random mode is enabled */
+ i = queue.PositionToOrder(song);
+
+ if (!playing)
+ current = 0;
+
+ /* swap the new song with the previous "current" one,
+ so playback continues as planned */
+ queue.SwapOrders(i, current);
+ i = current;
+ }
+
+ stop_on_error = false;
+ error_count = 0;
+
+ PlayOrder(pc, i);
+ return PLAYLIST_RESULT_SUCCESS;
+}
+
+enum playlist_result
+playlist::PlayId(player_control &pc, int id)
+{
+ if (id == -1)
+ return PlayPosition(pc, id);
+
+ int song = queue.IdToPosition(id);
+ if (song < 0)
+ return PLAYLIST_RESULT_NO_SUCH_SONG;
+
+ return PlayPosition(pc, song);
+}
+
+void
+playlist::PlayNext(player_control &pc)
+{
+ if (!playing)
+ return;
+
+ assert(!queue.IsEmpty());
+ assert(queue.IsValidOrder(current));
+
+ const int old_current = current;
+ stop_on_error = false;
+
+ /* determine the next song from the queue's order list */
+
+ const int next_order = queue.GetNextOrder(current);
+ if (next_order < 0) {
+ /* no song after this one: stop playback */
+ Stop(pc);
+
+ /* reset "current song" */
+ current = -1;
+ }
+ else
+ {
+ if (next_order == 0 && queue.random) {
+ /* The queue told us that the next song is the first
+ song. This means we are in repeat mode. Shuffle
+ the queue order, so this time, the user hears the
+ songs in a different than before */
+ assert(queue.repeat);
+
+ queue.ShuffleOrder();
+
+ /* note that current and queued are
+ now invalid, but playlist_play_order() will
+ discard them anyway */
+ }
+
+ PlayOrder(pc, next_order);
+ }
+
+ /* Consume mode removes each played songs. */
+ if (queue.consume)
+ DeleteOrder(pc, old_current);
+}
+
+void
+playlist::PlayPrevious(player_control &pc)
+{
+ if (!playing)
+ return;
+
+ assert(!queue.IsEmpty());
+
+ int order;
+ if (current > 0) {
+ /* play the preceding song */
+ order = current - 1;
+ } else if (queue.repeat) {
+ /* play the last song in "repeat" mode */
+ order = queue.GetLength() - 1;
+ } else {
+ /* re-start playing the current song if it's
+ the first one */
+ order = current;
+ }
+
+ PlayOrder(pc, order);
+}
+
+enum playlist_result
+playlist::SeekSongPosition(player_control &pc, unsigned song, float seek_time)
+{
+ if (!queue.IsValidPosition(song))
+ return PLAYLIST_RESULT_BAD_RANGE;
+
+ const Song *queued_song = GetQueuedSong();
+
+ unsigned i = queue.random
+ ? queue.PositionToOrder(song)
+ : song;
+
+ pc.ClearError();
+ stop_on_error = true;
+ error_count = 0;
+
+ if (!playing || (unsigned)current != i) {
+ /* seeking is not within the current song - prepare
+ song change */
+
+ playing = true;
+ current = i;
+
+ queued_song = nullptr;
+ }
+
+ Song *the_song = queue.GetOrder(i)->DupDetached();
+ if (!pc.Seek(the_song, seek_time)) {
+ UpdateQueuedSong(pc, queued_song);
+
+ return PLAYLIST_RESULT_NOT_PLAYING;
+ }
+
+ queued = -1;
+ UpdateQueuedSong(pc, NULL);
+
+ return PLAYLIST_RESULT_SUCCESS;
+}
+
+enum playlist_result
+playlist::SeekSongId(player_control &pc, unsigned id, float seek_time)
+{
+ int song = queue.IdToPosition(id);
+ if (song < 0)
+ return PLAYLIST_RESULT_NO_SUCH_SONG;
+
+ return SeekSongPosition(pc, song, seek_time);
+}
+
+enum playlist_result
+playlist::SeekCurrent(player_control &pc, float seek_time, bool relative)
+{
+ if (!playing)
+ return PLAYLIST_RESULT_NOT_PLAYING;
+
+ if (relative) {
+ const auto status = pc.GetStatus();
+
+ if (status.state != PLAYER_STATE_PLAY &&
+ status.state != PLAYER_STATE_PAUSE)
+ return PLAYLIST_RESULT_NOT_PLAYING;
+
+ seek_time += (int)status.elapsed_time;
+ }
+
+ if (seek_time < 0)
+ seek_time = 0;
+
+ return SeekSongPosition(pc, current, seek_time);
+}
diff --git a/src/PlaylistDatabase.cxx b/src/PlaylistDatabase.cxx
new file mode 100644
index 000000000..c5cfc8397
--- /dev/null
+++ b/src/PlaylistDatabase.cxx
@@ -0,0 +1,77 @@
+/*
+ * 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 "PlaylistDatabase.hxx"
+#include "PlaylistVector.hxx"
+#include "TextFile.hxx"
+#include "util/StringUtil.hxx"
+
+#include <string.h>
+#include <stdlib.h>
+
+static GQuark
+playlist_database_quark(void)
+{
+ return g_quark_from_static_string("playlist_database");
+}
+
+void
+playlist_vector_save(FILE *fp, const PlaylistVector &pv)
+{
+ for (const PlaylistInfo &pi : pv)
+ fprintf(fp, PLAYLIST_META_BEGIN "%s\n"
+ "mtime: %li\n"
+ "playlist_end\n",
+ pi.name.c_str(), (long)pi.mtime);
+}
+
+bool
+playlist_metadata_load(TextFile &file, PlaylistVector &pv, const char *name,
+ GError **error_r)
+{
+ PlaylistInfo pm(name, 0);
+
+ char *line, *colon;
+ const char *value;
+
+ while ((line = file.ReadLine()) != NULL &&
+ strcmp(line, "playlist_end") != 0) {
+ colon = strchr(line, ':');
+ if (colon == NULL || colon == line) {
+ g_set_error(error_r, playlist_database_quark(), 0,
+ "unknown line in db: %s", line);
+ return false;
+ }
+
+ *colon++ = 0;
+ value = strchug_fast_c(colon);
+
+ if (strcmp(line, "mtime") == 0)
+ pm.mtime = strtol(value, NULL, 10);
+ else {
+ g_set_error(error_r, playlist_database_quark(), 0,
+ "unknown line in db: %s", line);
+ return false;
+ }
+ }
+
+ pv.UpdateOrInsert(std::move(pm));
+ return true;
+}
diff --git a/src/PlaylistDatabase.hxx b/src/PlaylistDatabase.hxx
new file mode 100644
index 000000000..a08d623fb
--- /dev/null
+++ b/src/PlaylistDatabase.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_PLAYLIST_DATABASE_HXX
+#define MPD_PLAYLIST_DATABASE_HXX
+
+#include "check.h"
+#include "gerror.h"
+
+#include <stdio.h>
+
+#define PLAYLIST_META_BEGIN "playlist_begin: "
+
+class PlaylistVector;
+class TextFile;
+
+void
+playlist_vector_save(FILE *fp, const PlaylistVector &pv);
+
+bool
+playlist_metadata_load(TextFile &file, PlaylistVector &pv, const char *name,
+ GError **error_r);
+
+#endif
diff --git a/src/PlaylistEdit.cxx b/src/PlaylistEdit.cxx
new file mode 100644
index 000000000..0d46f66aa
--- /dev/null
+++ b/src/PlaylistEdit.cxx
@@ -0,0 +1,424 @@
+/*
+ * 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 "PlayerControl.hxx"
+#include "util/UriUtil.hxx"
+#include "Song.hxx"
+#include "Idle.hxx"
+#include "DatabaseGlue.hxx"
+#include "DatabasePlugin.hxx"
+
+#include <stdlib.h>
+
+void
+playlist::OnModified()
+{
+ queue.IncrementVersion();
+
+ idle_add(IDLE_PLAYLIST);
+}
+
+void
+playlist::Clear(player_control &pc)
+{
+ Stop(pc);
+
+ queue.Clear();
+ current = -1;
+
+ OnModified();
+}
+
+enum playlist_result
+playlist::AppendFile(struct player_control &pc,
+ const char *path_utf8, unsigned *added_id)
+{
+ Song *song = Song::LoadFile(path_utf8, nullptr);
+ if (song == NULL)
+ return PLAYLIST_RESULT_NO_SUCH_SONG;
+
+ return AppendSong(pc, song, added_id);
+}
+
+enum playlist_result
+playlist::AppendSong(struct player_control &pc,
+ Song *song, unsigned *added_id)
+{
+ unsigned id;
+
+ if (queue.IsFull())
+ return PLAYLIST_RESULT_TOO_LARGE;
+
+ const Song *const queued_song = GetQueuedSong();
+
+ id = queue.Append(song, 0);
+
+ if (queue.random) {
+ /* shuffle the new song into the list of remaining
+ songs to play */
+
+ unsigned start;
+ if (queued >= 0)
+ start = queued + 1;
+ else
+ start = current + 1;
+ if (start < queue.GetLength())
+ queue.ShuffleOrderLast(start, queue.GetLength());
+ }
+
+ UpdateQueuedSong(pc, queued_song);
+ OnModified();
+
+ if (added_id)
+ *added_id = id;
+
+ return PLAYLIST_RESULT_SUCCESS;
+}
+
+enum playlist_result
+playlist::AppendURI(struct player_control &pc,
+ const char *uri, unsigned *added_id)
+{
+ g_debug("add to playlist: %s", uri);
+
+ const Database *db = nullptr;
+ Song *song;
+ if (uri_has_scheme(uri)) {
+ song = Song::NewRemote(uri);
+ } else {
+ db = GetDatabase(nullptr);
+ if (db == nullptr)
+ return PLAYLIST_RESULT_NO_SUCH_SONG;
+
+ song = db->GetSong(uri, nullptr);
+ if (song == nullptr)
+ return PLAYLIST_RESULT_NO_SUCH_SONG;
+ }
+
+ enum playlist_result result = AppendSong(pc, song, added_id);
+ if (db != nullptr)
+ db->ReturnSong(song);
+
+ return result;
+}
+
+enum playlist_result
+playlist::SwapPositions(player_control &pc, unsigned song1, unsigned song2)
+{
+ if (!queue.IsValidPosition(song1) || !queue.IsValidPosition(song2))
+ return PLAYLIST_RESULT_BAD_RANGE;
+
+ const Song *const queued_song = GetQueuedSong();
+
+ queue.SwapPositions(song1, song2);
+
+ if (queue.random) {
+ /* update the queue order, so that current
+ still points to the current song order */
+
+ queue.SwapOrders(queue.PositionToOrder(song1),
+ queue.PositionToOrder(song2));
+ } else {
+ /* correct the "current" song order */
+
+ if (current == (int)song1)
+ current = song2;
+ else if (current == (int)song2)
+ current = song1;
+ }
+
+ UpdateQueuedSong(pc, queued_song);
+ OnModified();
+
+ return PLAYLIST_RESULT_SUCCESS;
+}
+
+enum playlist_result
+playlist::SwapIds(player_control &pc, unsigned id1, unsigned id2)
+{
+ int song1 = queue.IdToPosition(id1);
+ int song2 = queue.IdToPosition(id2);
+
+ if (song1 < 0 || song2 < 0)
+ return PLAYLIST_RESULT_NO_SUCH_SONG;
+
+ return SwapPositions(pc, song1, song2);
+}
+
+enum playlist_result
+playlist::SetPriorityRange(player_control &pc,
+ unsigned start, unsigned end,
+ uint8_t priority)
+{
+ if (start >= GetLength())
+ return PLAYLIST_RESULT_BAD_RANGE;
+
+ if (end > GetLength())
+ end = GetLength();
+
+ if (start >= end)
+ return PLAYLIST_RESULT_SUCCESS;
+
+ /* remember "current" and "queued" */
+
+ const int current_position = GetCurrentPosition();
+ const Song *const queued_song = GetQueuedSong();
+
+ /* apply the priority changes */
+
+ queue.SetPriorityRange(start, end, priority, current);
+
+ /* restore "current" and choose a new "queued" */
+
+ if (current_position >= 0)
+ current = queue.PositionToOrder(current_position);
+
+ UpdateQueuedSong(pc, queued_song);
+ OnModified();
+
+ return PLAYLIST_RESULT_SUCCESS;
+}
+
+enum playlist_result
+playlist::SetPriorityId(struct player_control &pc,
+ unsigned song_id, uint8_t priority)
+{
+ int song_position = queue.IdToPosition(song_id);
+ if (song_position < 0)
+ return PLAYLIST_RESULT_NO_SUCH_SONG;
+
+ return SetPriorityRange(pc, song_position, song_position + 1,
+ priority);
+
+}
+
+void
+playlist::DeleteInternal(player_control &pc,
+ unsigned song, const Song **queued_p)
+{
+ assert(song < GetLength());
+
+ unsigned songOrder = queue.PositionToOrder(song);
+
+ if (playing && current == (int)songOrder) {
+ const bool paused = pc.GetState() == PLAYER_STATE_PAUSE;
+
+ /* the current song is going to be deleted: stop the player */
+
+ pc.Stop();
+ playing = false;
+
+ /* see which song is going to be played instead */
+
+ current = queue.GetNextOrder(current);
+ if (current == (int)songOrder)
+ current = -1;
+
+ if (current >= 0 && !paused)
+ /* play the song after the deleted one */
+ PlayOrder(pc, current);
+ else
+ /* no songs left to play, stop playback
+ completely */
+ Stop(pc);
+
+ *queued_p = NULL;
+ } else if (current == (int)songOrder)
+ /* there's a "current song" but we're not playing
+ currently - clear "current" */
+ current = -1;
+
+ /* now do it: remove the song */
+
+ queue.DeletePosition(song);
+
+ /* update the "current" and "queued" variables */
+
+ if (current > (int)songOrder)
+ current--;
+}
+
+enum playlist_result
+playlist::DeletePosition(struct player_control &pc, unsigned song)
+{
+ if (song >= queue.GetLength())
+ return PLAYLIST_RESULT_BAD_RANGE;
+
+ const Song *queued_song = GetQueuedSong();
+
+ DeleteInternal(pc, song, &queued_song);
+
+ UpdateQueuedSong(pc, queued_song);
+ OnModified();
+
+ return PLAYLIST_RESULT_SUCCESS;
+}
+
+enum playlist_result
+playlist::DeleteRange(struct player_control &pc, unsigned start, unsigned end)
+{
+ if (start >= queue.GetLength())
+ return PLAYLIST_RESULT_BAD_RANGE;
+
+ if (end > queue.GetLength())
+ end = queue.GetLength();
+
+ if (start >= end)
+ return PLAYLIST_RESULT_SUCCESS;
+
+ const Song *queued_song = GetQueuedSong();
+
+ do {
+ DeleteInternal(pc, --end, &queued_song);
+ } while (end != start);
+
+ UpdateQueuedSong(pc, queued_song);
+ OnModified();
+
+ return PLAYLIST_RESULT_SUCCESS;
+}
+
+enum playlist_result
+playlist::DeleteId(struct player_control &pc, unsigned id)
+{
+ int song = queue.IdToPosition(id);
+ if (song < 0)
+ return PLAYLIST_RESULT_NO_SUCH_SONG;
+
+ return DeletePosition(pc, song);
+}
+
+void
+playlist::DeleteSong(struct player_control &pc, const struct Song &song)
+{
+ for (int i = queue.GetLength() - 1; i >= 0; --i)
+ // TODO: compare URI instead of pointer
+ if (&song == queue.Get(i))
+ DeletePosition(pc, i);
+}
+
+enum playlist_result
+playlist::MoveRange(player_control &pc, unsigned start, unsigned end, int to)
+{
+ if (!queue.IsValidPosition(start) || !queue.IsValidPosition(end - 1))
+ return PLAYLIST_RESULT_BAD_RANGE;
+
+ if ((to >= 0 && to + end - start - 1 >= GetLength()) ||
+ (to < 0 && unsigned(abs(to)) > GetLength()))
+ return PLAYLIST_RESULT_BAD_RANGE;
+
+ if ((int)start == to)
+ /* nothing happens */
+ return PLAYLIST_RESULT_SUCCESS;
+
+ const Song *const queued_song = GetQueuedSong();
+
+ /*
+ * (to < 0) => move to offset from current song
+ * (-playlist.length == to) => move to position BEFORE current song
+ */
+ const int currentSong = GetCurrentPosition();
+ if (to < 0) {
+ if (currentSong < 0)
+ /* can't move relative to current song,
+ because there is no current song */
+ return PLAYLIST_RESULT_BAD_RANGE;
+
+ if (start <= (unsigned)currentSong && (unsigned)currentSong < end)
+ /* no-op, can't be moved to offset of itself */
+ return PLAYLIST_RESULT_SUCCESS;
+ to = (currentSong + abs(to)) % GetLength();
+ if (start < (unsigned)to)
+ to--;
+ }
+
+ queue.MoveRange(start, end, to);
+
+ if (!queue.random) {
+ /* update current/queued */
+ if ((int)start <= current && (unsigned)current < end)
+ current += to - start;
+ else if (current >= (int)end && current <= to)
+ current -= end - start;
+ else if (current >= to && current < (int)start)
+ current += end - start;
+ }
+
+ UpdateQueuedSong(pc, queued_song);
+ OnModified();
+
+ return PLAYLIST_RESULT_SUCCESS;
+}
+
+enum playlist_result
+playlist::MoveId(player_control &pc, unsigned id1, int to)
+{
+ int song = queue.IdToPosition(id1);
+ if (song < 0)
+ return PLAYLIST_RESULT_NO_SUCH_SONG;
+
+ return MoveRange(pc, song, song + 1, to);
+}
+
+void
+playlist::Shuffle(player_control &pc, unsigned start, unsigned end)
+{
+ if (end > GetLength())
+ /* correct the "end" offset */
+ end = GetLength();
+
+ if (start + 1 >= end)
+ /* needs at least two entries. */
+ return;
+
+ const Song *const queued_song = GetQueuedSong();
+ if (playing && current >= 0) {
+ unsigned current_position = queue.OrderToPosition(current);
+
+ if (current_position >= start && current_position < end) {
+ /* put current playing song first */
+ queue.SwapPositions(start, current_position);
+
+ if (queue.random) {
+ current = queue.PositionToOrder(start);
+ } else
+ current = start;
+
+ /* start shuffle after the current song */
+ start++;
+ }
+ } else {
+ /* no playback currently: reset current */
+
+ current = -1;
+ }
+
+ queue.ShuffleRange(start, end);
+
+ UpdateQueuedSong(pc, queued_song);
+ OnModified();
+}
diff --git a/src/PlaylistFile.cxx b/src/PlaylistFile.cxx
new file mode 100644
index 000000000..6541e6598
--- /dev/null
+++ b/src/PlaylistFile.cxx
@@ -0,0 +1,466 @@
+/*
+ * 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 "PlaylistFile.hxx"
+#include "PlaylistSave.hxx"
+#include "PlaylistInfo.hxx"
+#include "PlaylistVector.hxx"
+#include "DatabasePlugin.hxx"
+#include "DatabaseGlue.hxx"
+#include "Song.hxx"
+#include "io_error.h"
+#include "Mapper.hxx"
+#include "TextFile.hxx"
+#include "conf.h"
+#include "Idle.hxx"
+#include "fs/Path.hxx"
+#include "fs/FileSystem.hxx"
+#include "fs/DirectoryReader.hxx"
+#include "util/UriUtil.hxx"
+
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <string.h>
+#include <errno.h>
+
+static const char PLAYLIST_COMMENT = '#';
+
+static unsigned playlist_max_length;
+bool playlist_saveAbsolutePaths = DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS;
+
+void
+spl_global_init(void)
+{
+ playlist_max_length = config_get_positive(CONF_MAX_PLAYLIST_LENGTH,
+ DEFAULT_PLAYLIST_MAX_LENGTH);
+
+ playlist_saveAbsolutePaths =
+ config_get_bool(CONF_SAVE_ABSOLUTE_PATHS,
+ DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS);
+}
+
+bool
+spl_valid_name(const char *name_utf8)
+{
+ /*
+ * Not supporting '/' was done out of laziness, and we should
+ * really strive to support it in the future.
+ *
+ * Not supporting '\r' and '\n' is done out of protocol
+ * limitations (and arguably laziness), but bending over head
+ * over heels to modify the protocol (and compatibility with
+ * all clients) to support idiots who put '\r' and '\n' in
+ * filenames isn't going to happen, either.
+ */
+
+ return strchr(name_utf8, '/') == NULL &&
+ strchr(name_utf8, '\n') == NULL &&
+ strchr(name_utf8, '\r') == NULL;
+}
+
+static const Path &
+spl_map(GError **error_r)
+{
+ const Path &path_fs = map_spl_path();
+ if (path_fs.IsNull())
+ g_set_error_literal(error_r, playlist_quark(),
+ PLAYLIST_RESULT_DISABLED,
+ "Stored playlists are disabled");
+ return path_fs;
+}
+
+static bool
+spl_check_name(const char *name_utf8, GError **error_r)
+{
+ if (!spl_valid_name(name_utf8)) {
+ g_set_error_literal(error_r, playlist_quark(),
+ PLAYLIST_RESULT_BAD_NAME,
+ "Bad playlist name");
+ return false;
+ }
+
+ return true;
+}
+
+static Path
+spl_map_to_fs(const char *name_utf8, GError **error_r)
+{
+ if (spl_map(error_r).IsNull() || !spl_check_name(name_utf8, error_r))
+ return Path::Null();
+
+ Path path_fs = map_spl_utf8_to_fs(name_utf8);
+ if (path_fs.IsNull())
+ g_set_error_literal(error_r, playlist_quark(),
+ PLAYLIST_RESULT_BAD_NAME,
+ "Bad playlist name");
+
+ return path_fs;
+}
+
+/**
+ * Create a GError for the current errno.
+ */
+static void
+playlist_errno(GError **error_r)
+{
+ switch (errno) {
+ case ENOENT:
+ g_set_error_literal(error_r, playlist_quark(),
+ PLAYLIST_RESULT_NO_SUCH_LIST,
+ "No such playlist");
+ break;
+
+ default:
+ set_error_errno(error_r);
+ break;
+ }
+}
+
+static bool
+LoadPlaylistFileInfo(PlaylistInfo &info,
+ const Path &parent_path_fs, const Path &name_fs)
+{
+ const char *name_fs_str = name_fs.c_str();
+ size_t name_length = strlen(name_fs_str);
+
+ if (name_length < sizeof(PLAYLIST_FILE_SUFFIX) ||
+ memchr(name_fs_str, '\n', name_length) != NULL)
+ return false;
+
+ if (!g_str_has_suffix(name_fs_str, PLAYLIST_FILE_SUFFIX))
+ return false;
+
+ Path path_fs = Path::Build(parent_path_fs, name_fs);
+ struct stat st;
+ 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 = Path::ToUTF8(name);
+ g_free(name);
+ if (name_utf8.empty())
+ return false;
+
+ info.name = std::move(name_utf8);
+ info.mtime = st.st_mtime;
+ return true;
+}
+
+PlaylistVector
+ListPlaylistFiles(GError **error_r)
+{
+ PlaylistVector list;
+
+ const Path &parent_path_fs = spl_map(error_r);
+ if (parent_path_fs.IsNull())
+ return list;
+
+ DirectoryReader reader(parent_path_fs);
+ if (reader.HasFailed()) {
+ set_error_errno(error_r);
+ return list;
+ }
+
+ PlaylistInfo info;
+ while (reader.ReadEntry()) {
+ const Path entry = reader.GetEntry();
+ if (LoadPlaylistFileInfo(info, parent_path_fs, entry))
+ list.push_back(std::move(info));
+ }
+
+ return list;
+}
+
+static bool
+SavePlaylistFile(const PlaylistFileContents &contents, const char *utf8path,
+ GError **error_r)
+{
+ assert(utf8path != NULL);
+
+ if (spl_map(error_r).IsNull())
+ return false;
+
+ const Path path_fs = spl_map_to_fs(utf8path, error_r);
+ if (path_fs.IsNull())
+ return false;
+
+ FILE *file = FOpen(path_fs, FOpenMode::WriteText);
+ if (file == NULL) {
+ playlist_errno(error_r);
+ return false;
+ }
+
+ for (const auto &uri_utf8 : contents)
+ playlist_print_uri(file, uri_utf8.c_str());
+
+ fclose(file);
+ return true;
+}
+
+PlaylistFileContents
+LoadPlaylistFile(const char *utf8path, GError **error_r)
+{
+ PlaylistFileContents contents;
+
+ if (spl_map(error_r).IsNull())
+ return contents;
+
+ const Path path_fs = spl_map_to_fs(utf8path, error_r);
+ if (path_fs.IsNull())
+ return contents;
+
+ TextFile file(path_fs);
+ if (file.HasFailed()) {
+ playlist_errno(error_r);
+ return contents;
+ }
+
+ char *s;
+ while ((s = file.ReadLine()) != NULL) {
+ if (*s == 0 || *s == PLAYLIST_COMMENT)
+ continue;
+
+ if (!uri_has_scheme(s)) {
+ char *path_utf8;
+
+ path_utf8 = map_fs_to_utf8(s);
+ if (path_utf8 == NULL)
+ continue;
+
+ s = path_utf8;
+ } else
+ s = g_strdup(s);
+
+ contents.emplace_back(s);
+ if (contents.size() >= playlist_max_length)
+ break;
+ }
+
+ return contents;
+}
+
+bool
+spl_move_index(const char *utf8path, unsigned src, unsigned dest,
+ GError **error_r)
+{
+ if (src == dest)
+ /* this doesn't check whether the playlist exists, but
+ what the hell.. */
+ return true;
+
+ GError *error = nullptr;
+ auto contents = LoadPlaylistFile(utf8path, &error);
+ if (contents.empty() && error != nullptr) {
+ g_propagate_error(error_r, error);
+ return false;
+ }
+
+ if (src >= contents.size() || dest >= contents.size()) {
+ g_set_error_literal(error_r, playlist_quark(),
+ PLAYLIST_RESULT_BAD_RANGE,
+ "Bad range");
+ return false;
+ }
+
+ const auto src_i = std::next(contents.begin(), src);
+ auto value = std::move(*src_i);
+ contents.erase(src_i);
+
+ const auto dest_i = std::next(contents.begin(), dest);
+ contents.insert(dest_i, std::move(value));
+
+ bool result = SavePlaylistFile(contents, utf8path, error_r);
+
+ idle_add(IDLE_STORED_PLAYLIST);
+ return result;
+}
+
+bool
+spl_clear(const char *utf8path, GError **error_r)
+{
+ if (spl_map(error_r).IsNull())
+ return false;
+
+ const Path path_fs = spl_map_to_fs(utf8path, error_r);
+ if (path_fs.IsNull())
+ return false;
+
+ FILE *file = FOpen(path_fs, FOpenMode::WriteText);
+ if (file == NULL) {
+ playlist_errno(error_r);
+ return false;
+ }
+
+ fclose(file);
+
+ idle_add(IDLE_STORED_PLAYLIST);
+ return true;
+}
+
+bool
+spl_delete(const char *name_utf8, GError **error_r)
+{
+ const Path path_fs = spl_map_to_fs(name_utf8, error_r);
+ if (path_fs.IsNull())
+ return false;
+
+ if (!RemoveFile(path_fs)) {
+ playlist_errno(error_r);
+ return false;
+ }
+
+ idle_add(IDLE_STORED_PLAYLIST);
+ return true;
+}
+
+bool
+spl_remove_index(const char *utf8path, unsigned pos, GError **error_r)
+{
+ GError *error = nullptr;
+ auto contents = LoadPlaylistFile(utf8path, &error);
+ if (contents.empty() && error != nullptr) {
+ g_propagate_error(error_r, error);
+ return false;
+ }
+
+ if (pos >= contents.size()) {
+ g_set_error_literal(error_r, playlist_quark(),
+ PLAYLIST_RESULT_BAD_RANGE,
+ "Bad range");
+ return false;
+ }
+
+ contents.erase(std::next(contents.begin(), pos));
+
+ bool result = SavePlaylistFile(contents, utf8path, error_r);
+
+ idle_add(IDLE_STORED_PLAYLIST);
+ return result;
+}
+
+bool
+spl_append_song(const char *utf8path, Song *song, GError **error_r)
+{
+ if (spl_map(error_r).IsNull())
+ return false;
+
+ const Path path_fs = spl_map_to_fs(utf8path, error_r);
+ if (path_fs.IsNull())
+ return false;
+
+ FILE *file = FOpen(path_fs, FOpenMode::AppendText);
+ if (file == NULL) {
+ playlist_errno(error_r);
+ return false;
+ }
+
+ struct stat st;
+ if (fstat(fileno(file), &st) < 0) {
+ playlist_errno(error_r);
+ fclose(file);
+ return false;
+ }
+
+ if (st.st_size / (MPD_PATH_MAX + 1) >= (off_t)playlist_max_length) {
+ fclose(file);
+ g_set_error_literal(error_r, playlist_quark(),
+ PLAYLIST_RESULT_TOO_LARGE,
+ "Stored playlist is too large");
+ return false;
+ }
+
+ playlist_print_song(file, song);
+
+ fclose(file);
+
+ idle_add(IDLE_STORED_PLAYLIST);
+ return true;
+}
+
+bool
+spl_append_uri(const char *url, const char *utf8file, GError **error_r)
+{
+ if (uri_has_scheme(url)) {
+ Song *song = Song::NewRemote(url);
+ bool success = spl_append_song(utf8file, song, error_r);
+ song->Free();
+ return success;
+ } else {
+ const Database *db = GetDatabase(error_r);
+ if (db == nullptr)
+ return false;
+
+ Song *song = db->GetSong(url, error_r);
+ if (song == nullptr)
+ return false;
+
+ bool success = spl_append_song(utf8file, song, error_r);
+ db->ReturnSong(song);
+ return success;
+ }
+}
+
+static bool
+spl_rename_internal(const Path &from_path_fs, const Path &to_path_fs,
+ GError **error_r)
+{
+ if (!FileExists(from_path_fs)) {
+ g_set_error_literal(error_r, playlist_quark(),
+ PLAYLIST_RESULT_NO_SUCH_LIST,
+ "No such playlist");
+ return false;
+ }
+
+ if (FileExists(to_path_fs)) {
+ g_set_error_literal(error_r, playlist_quark(),
+ PLAYLIST_RESULT_LIST_EXISTS,
+ "Playlist exists already");
+ return false;
+ }
+
+ if (!RenameFile(from_path_fs, to_path_fs)) {
+ playlist_errno(error_r);
+ return false;
+ }
+
+ idle_add(IDLE_STORED_PLAYLIST);
+ return true;
+}
+
+bool
+spl_rename(const char *utf8from, const char *utf8to, GError **error_r)
+{
+ if (spl_map(error_r).IsNull())
+ return false;
+
+ Path from_path_fs = spl_map_to_fs(utf8from, error_r);
+ if (from_path_fs.IsNull())
+ return false;
+
+ Path to_path_fs = spl_map_to_fs(utf8to, error_r);
+ if (to_path_fs.IsNull())
+ return false;
+
+ return spl_rename_internal(from_path_fs, to_path_fs, error_r);
+}
diff --git a/src/PlaylistFile.hxx b/src/PlaylistFile.hxx
new file mode 100644
index 000000000..4fcecc8da
--- /dev/null
+++ b/src/PlaylistFile.hxx
@@ -0,0 +1,85 @@
+/*
+ * 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.
+ */
+
+#ifndef MPD_PLAYLIST_FILE_HXX
+#define MPD_PLAYLIST_FILE_HXX
+
+#include "gerror.h"
+
+#include <vector>
+#include <string>
+
+struct Song;
+struct PlaylistInfo;
+class PlaylistVector;
+
+typedef std::vector<std::string> PlaylistFileContents;
+
+extern bool playlist_saveAbsolutePaths;
+
+/**
+ * Perform some global initialization, e.g. load configuration values.
+ */
+void
+spl_global_init(void);
+
+#ifdef __cplusplus
+
+/**
+ * Determines whether the specified string is a valid name for a
+ * stored playlist.
+ */
+bool
+spl_valid_name(const char *name_utf8);
+
+/**
+ * Returns a list of stored_playlist_info struct pointers. Returns
+ * NULL if an error occurred.
+ */
+PlaylistVector
+ListPlaylistFiles(GError **error_r);
+
+PlaylistFileContents
+LoadPlaylistFile(const char *utf8path, GError **error_r);
+
+bool
+spl_move_index(const char *utf8path, unsigned src, unsigned dest,
+ GError **error_r);
+
+bool
+spl_clear(const char *utf8path, GError **error_r);
+
+bool
+spl_delete(const char *name_utf8, GError **error_r);
+
+bool
+spl_remove_index(const char *utf8path, unsigned pos, GError **error_r);
+
+bool
+spl_append_song(const char *utf8path, Song *song, GError **error_r);
+
+bool
+spl_append_uri(const char *file, const char *utf8file, GError **error_r);
+
+bool
+spl_rename(const char *utf8from, const char *utf8to, GError **error_r);
+
+#endif
+
+#endif
diff --git a/src/PlaylistGlobal.cxx b/src/PlaylistGlobal.cxx
new file mode 100644
index 000000000..97902275b
--- /dev/null
+++ b/src/PlaylistGlobal.cxx
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+
+/*
+ * The manager of the global "struct playlist" instance (g_playlist).
+ *
+ */
+
+#include "config.h"
+#include "PlaylistGlobal.hxx"
+#include "Playlist.hxx"
+#include "Main.hxx"
+#include "Instance.hxx"
+#include "GlobalEvents.hxx"
+
+static void
+playlist_tag_event(void)
+{
+ instance->TagModified();
+}
+
+static void
+playlist_event(void)
+{
+ instance->SyncWithPlayer();
+}
+
+void
+playlist_global_init()
+{
+ GlobalEvents::Register(GlobalEvents::TAG, playlist_tag_event);
+ GlobalEvents::Register(GlobalEvents::PLAYLIST, playlist_event);
+}
diff --git a/src/PlaylistGlobal.hxx b/src/PlaylistGlobal.hxx
new file mode 100644
index 000000000..4397292db
--- /dev/null
+++ b/src/PlaylistGlobal.hxx
@@ -0,0 +1,26 @@
+/*
+ * 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_PLAYLIST_GLOBAL_HXX
+#define MPD_PLAYLIST_GLOBAL_HXX
+
+void
+playlist_global_init();
+
+#endif
diff --git a/src/PlaylistInfo.hxx b/src/PlaylistInfo.hxx
new file mode 100644
index 000000000..96e4f6db9
--- /dev/null
+++ b/src/PlaylistInfo.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_PLAYLIST_INFO_HXX
+#define MPD_PLAYLIST_INFO_HXX
+
+#include "check.h"
+#include "gcc.h"
+
+#include <string>
+
+#include <sys/time.h>
+
+/**
+ * A directory entry pointing to a playlist file.
+ */
+struct PlaylistInfo {
+ /**
+ * The UTF-8 encoded name of the playlist file.
+ */
+ std::string name;
+
+ time_t mtime;
+
+ class CompareName {
+ const char *const name;
+
+ public:
+ constexpr CompareName(const char *_name):name(_name) {}
+
+ gcc_pure
+ bool operator()(const PlaylistInfo &pi) const {
+ return pi.name.compare(name) == 0;
+ }
+ };
+
+ PlaylistInfo() = default;
+
+ template<typename N>
+ PlaylistInfo(N &&_name, time_t _mtime)
+ :name(std::forward<N>(_name)), mtime(_mtime) {}
+
+ PlaylistInfo(const PlaylistInfo &other) = delete;
+ PlaylistInfo(PlaylistInfo &&) = default;
+};
+
+#endif
diff --git a/src/PlaylistMapper.cxx b/src/PlaylistMapper.cxx
new file mode 100644
index 000000000..08131106d
--- /dev/null
+++ b/src/PlaylistMapper.cxx
@@ -0,0 +1,105 @@
+/*
+ * 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 "PlaylistMapper.hxx"
+#include "PlaylistFile.hxx"
+#include "PlaylistRegistry.hxx"
+#include "Mapper.hxx"
+#include "fs/Path.hxx"
+#include "util/UriUtil.hxx"
+
+#include <assert.h>
+
+static struct playlist_provider *
+playlist_open_path(const char *path_fs, Mutex &mutex, Cond &cond,
+ struct input_stream **is_r)
+{
+ struct playlist_provider *playlist;
+
+ playlist = playlist_list_open_uri(path_fs, mutex, cond);
+ if (playlist != NULL)
+ *is_r = NULL;
+ else
+ playlist = playlist_list_open_path(path_fs, mutex, cond, is_r);
+
+ return playlist;
+}
+
+/**
+ * Load a playlist from the configured playlist directory.
+ */
+static struct playlist_provider *
+playlist_open_in_playlist_dir(const char *uri, Mutex &mutex, Cond &cond,
+ struct input_stream **is_r)
+{
+ char *path_fs;
+
+ assert(spl_valid_name(uri));
+
+ const Path &playlist_directory_fs = map_spl_path();
+ if (playlist_directory_fs.IsNull())
+ return NULL;
+
+ path_fs = g_build_filename(playlist_directory_fs.c_str(), uri, NULL);
+
+ struct playlist_provider *playlist =
+ playlist_open_path(path_fs, mutex, cond, is_r);
+ g_free(path_fs);
+
+ return playlist;
+}
+
+/**
+ * Load a playlist from the configured music directory.
+ */
+static struct playlist_provider *
+playlist_open_in_music_dir(const char *uri, Mutex &mutex, Cond &cond,
+ struct input_stream **is_r)
+{
+ assert(uri_safe_local(uri));
+
+ Path path = map_uri_fs(uri);
+ if (path.IsNull())
+ return NULL;
+
+ return playlist_open_path(path.c_str(), mutex, cond, is_r);
+}
+
+struct playlist_provider *
+playlist_mapper_open(const char *uri, Mutex &mutex, Cond &cond,
+ struct input_stream **is_r)
+{
+ struct playlist_provider *playlist;
+
+ if (spl_valid_name(uri)) {
+ playlist = playlist_open_in_playlist_dir(uri, mutex, cond,
+ is_r);
+ if (playlist != NULL)
+ return playlist;
+ }
+
+ if (uri_safe_local(uri)) {
+ playlist = playlist_open_in_music_dir(uri, mutex, cond, is_r);
+ if (playlist != NULL)
+ return playlist;
+ }
+
+ return NULL;
+}
diff --git a/src/PlaylistMapper.hxx b/src/PlaylistMapper.hxx
new file mode 100644
index 000000000..abfdb5481
--- /dev/null
+++ b/src/PlaylistMapper.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_PLAYLIST_MAPPER_HXX
+#define MPD_PLAYLIST_MAPPER_HXX
+
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+
+struct input_stream;
+
+/**
+ * Opens a playlist from an URI relative to the playlist or music
+ * directory.
+ *
+ * @param is_r on success, an input_stream object may be returned
+ * here, which must be closed after the playlist_provider object is
+ * freed
+ */
+struct playlist_provider *
+playlist_mapper_open(const char *uri, Mutex &mutex, Cond &cond,
+ struct input_stream **is_r);
+
+#endif
diff --git a/src/PlaylistPlugin.hxx b/src/PlaylistPlugin.hxx
new file mode 100644
index 000000000..069b818ba
--- /dev/null
+++ b/src/PlaylistPlugin.hxx
@@ -0,0 +1,142 @@
+/*
+ * 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_PLAYLIST_PLUGIN_HXX
+#define MPD_PLAYLIST_PLUGIN_HXX
+
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+
+#include <stddef.h>
+
+struct config_param;
+struct input_stream;
+struct Tag;
+struct Song;
+
+/**
+ * An object which provides the contents of a playlist.
+ */
+struct playlist_provider {
+ const struct playlist_plugin *plugin;
+};
+
+static inline void
+playlist_provider_init(struct playlist_provider *playlist,
+ const struct playlist_plugin *plugin)
+{
+ playlist->plugin = plugin;
+}
+
+struct playlist_plugin {
+ const char *name;
+
+ /**
+ * Initialize the plugin. Optional method.
+ *
+ * @param param a configuration block for this plugin, or NULL
+ * if none is configured
+ * @return true if the plugin was initialized successfully,
+ * false if the plugin is not available
+ */
+ bool (*init)(const config_param &param);
+
+ /**
+ * Deinitialize a plugin which was initialized successfully.
+ * Optional method.
+ */
+ void (*finish)(void);
+
+ /**
+ * Opens the playlist on the specified URI. This URI has
+ * either matched one of the schemes or one of the suffixes.
+ */
+ struct playlist_provider *(*open_uri)(const char *uri,
+ Mutex &mutex, Cond &cond);
+
+ /**
+ * Opens the playlist in the specified input stream. It has
+ * either matched one of the suffixes or one of the MIME
+ * types.
+ */
+ struct playlist_provider *(*open_stream)(struct input_stream *is);
+
+ void (*close)(struct playlist_provider *playlist);
+
+ Song *(*read)(struct playlist_provider *playlist);
+
+ const char *const*schemes;
+ const char *const*suffixes;
+ const char *const*mime_types;
+};
+
+/**
+ * Initialize a plugin.
+ *
+ * @param param a configuration block for this plugin, or NULL if none
+ * is configured
+ * @return true if the plugin was initialized successfully, false if
+ * the plugin is not available
+ */
+static inline bool
+playlist_plugin_init(const struct playlist_plugin *plugin,
+ const config_param &param)
+{
+ return plugin->init != NULL
+ ? plugin->init(param)
+ : true;
+}
+
+/**
+ * Deinitialize a plugin which was initialized successfully.
+ */
+static inline void
+playlist_plugin_finish(const struct playlist_plugin *plugin)
+{
+ if (plugin->finish != NULL)
+ plugin->finish();
+}
+
+static inline struct playlist_provider *
+playlist_plugin_open_uri(const struct playlist_plugin *plugin, const char *uri,
+ Mutex &mutex, Cond &cond)
+{
+ return plugin->open_uri(uri, mutex, cond);
+}
+
+static inline struct playlist_provider *
+playlist_plugin_open_stream(const struct playlist_plugin *plugin,
+ struct input_stream *is)
+{
+ return plugin->open_stream(is);
+}
+
+static inline void
+playlist_plugin_close(struct playlist_provider *playlist)
+{
+ playlist->plugin->close(playlist);
+}
+
+static inline Song *
+playlist_plugin_read(struct playlist_provider *playlist)
+{
+ return playlist->plugin->read(playlist);
+}
+
+#endif
diff --git a/src/PlaylistPrint.cxx b/src/PlaylistPrint.cxx
new file mode 100644
index 000000000..35498eeba
--- /dev/null
+++ b/src/PlaylistPrint.cxx
@@ -0,0 +1,190 @@
+/*
+ * 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 "PlaylistPrint.hxx"
+#include "PlaylistFile.hxx"
+#include "PlaylistAny.hxx"
+#include "PlaylistSong.hxx"
+#include "Playlist.hxx"
+#include "PlaylistRegistry.hxx"
+#include "PlaylistPlugin.hxx"
+#include "QueuePrint.hxx"
+#include "SongPrint.hxx"
+#include "DatabaseGlue.hxx"
+#include "DatabasePlugin.hxx"
+#include "Client.hxx"
+#include "input_stream.h"
+#include "Song.hxx"
+
+void
+playlist_print_uris(Client *client, const struct playlist *playlist)
+{
+ const struct queue *queue = &playlist->queue;
+
+ queue_print_uris(client, queue, 0, queue->GetLength());
+}
+
+bool
+playlist_print_info(Client *client, const struct playlist *playlist,
+ unsigned start, unsigned end)
+{
+ const struct queue *queue = &playlist->queue;
+
+ if (end > queue->GetLength())
+ /* correct the "end" offset */
+ end = queue->GetLength();
+
+ if (start > end)
+ /* an invalid "start" offset is fatal */
+ return false;
+
+ queue_print_info(client, queue, start, end);
+ return true;
+}
+
+bool
+playlist_print_id(Client *client, const struct playlist *playlist,
+ unsigned id)
+{
+ const struct queue *queue = &playlist->queue;
+ int position;
+
+ position = queue->IdToPosition(id);
+ if (position < 0)
+ /* no such song */
+ return false;
+
+ return playlist_print_info(client, playlist, position, position + 1);
+}
+
+bool
+playlist_print_current(Client *client, const struct playlist *playlist)
+{
+ int current_position = playlist->GetCurrentPosition();
+ if (current_position < 0)
+ return false;
+
+ queue_print_info(client, &playlist->queue,
+ current_position, current_position + 1);
+ return true;
+}
+
+void
+playlist_print_find(Client *client, const struct playlist *playlist,
+ const SongFilter &filter)
+{
+ queue_find(client, &playlist->queue, filter);
+}
+
+void
+playlist_print_changes_info(Client *client,
+ const struct playlist *playlist,
+ uint32_t version)
+{
+ queue_print_changes_info(client, &playlist->queue, version);
+}
+
+void
+playlist_print_changes_position(Client *client,
+ const struct playlist *playlist,
+ uint32_t version)
+{
+ queue_print_changes_position(client, &playlist->queue, version);
+}
+
+static bool
+PrintSongDetails(Client *client, const char *uri_utf8)
+{
+ const Database *db = GetDatabase(nullptr);
+ if (db == nullptr)
+ return false;
+
+ Song *song = db->GetSong(uri_utf8, nullptr);
+ if (song == nullptr)
+ return false;
+
+ song_print_info(client, song);
+ db->ReturnSong(song);
+ return true;
+}
+
+bool
+spl_print(Client *client, const char *name_utf8, bool detail,
+ GError **error_r)
+{
+ GError *error = NULL;
+ PlaylistFileContents contents = LoadPlaylistFile(name_utf8, &error);
+ if (contents.empty() && error != nullptr) {
+ g_propagate_error(error_r, error);
+ return false;
+ }
+
+ for (const auto &uri_utf8 : contents) {
+ if (!detail || !PrintSongDetails(client, uri_utf8.c_str()))
+ client_printf(client, SONG_FILE "%s\n",
+ uri_utf8.c_str());
+ }
+
+ return true;
+}
+
+static void
+playlist_provider_print(Client *client, const char *uri,
+ struct playlist_provider *playlist, bool detail)
+{
+ Song *song;
+ char *base_uri = uri != NULL ? g_path_get_dirname(uri) : NULL;
+
+ while ((song = playlist_plugin_read(playlist)) != NULL) {
+ song = playlist_check_translate_song(song, base_uri, false);
+ if (song == NULL)
+ continue;
+
+ if (detail)
+ song_print_info(client, song);
+ else
+ song_print_uri(client, song);
+
+ song->Free();
+ }
+
+ g_free(base_uri);
+}
+
+bool
+playlist_file_print(Client *client, const char *uri, bool detail)
+{
+ Mutex mutex;
+ Cond cond;
+
+ struct input_stream *is;
+ struct playlist_provider *playlist =
+ playlist_open_any(uri, mutex, cond, &is);
+ if (playlist == NULL)
+ return false;
+
+ playlist_provider_print(client, uri, playlist, detail);
+ playlist_plugin_close(playlist);
+
+ if (is != NULL)
+ input_stream_close(is);
+
+ return true;
+}
diff --git a/src/PlaylistPrint.hxx b/src/PlaylistPrint.hxx
new file mode 100644
index 000000000..16bee9b85
--- /dev/null
+++ b/src/PlaylistPrint.hxx
@@ -0,0 +1,110 @@
+/*
+ * 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.
+ */
+
+#ifndef MPD_PLAYLIST_PRINT_HXX
+#define MPD_PLAYLIST_PRINT_HXX
+
+#include "gerror.h"
+
+#include <stdint.h>
+
+struct playlist;
+class SongFilter;
+class Client;
+
+/**
+ * Sends the whole playlist to the client, song URIs only.
+ */
+void
+playlist_print_uris(Client *client, const struct playlist *playlist);
+
+/**
+ * Sends a range of the playlist to the client, including all known
+ * information about the songs. The "end" offset is decreased
+ * automatically if it is too large; passing UINT_MAX is allowed.
+ * This function however fails when the start offset is invalid.
+ */
+bool
+playlist_print_info(Client *client, const struct playlist *playlist,
+ unsigned start, unsigned end);
+
+/**
+ * Sends the song with the specified id to the client.
+ *
+ * @return true on suite, false if there is no such song
+ */
+bool
+playlist_print_id(Client *client, const struct playlist *playlist,
+ unsigned id);
+
+/**
+ * Sends the current song to the client.
+ *
+ * @return true on success, false if there is no current song
+ */
+bool
+playlist_print_current(Client *client, const struct playlist *playlist);
+
+/**
+ * Find songs in the playlist.
+ */
+void
+playlist_print_find(Client *client, const struct playlist *playlist,
+ const SongFilter &filter);
+
+/**
+ * Print detailed changes since the specified playlist version.
+ */
+void
+playlist_print_changes_info(Client *client,
+ const struct playlist *playlist,
+ uint32_t version);
+
+/**
+ * Print changes since the specified playlist version, position only.
+ */
+void
+playlist_print_changes_position(Client *client,
+ const struct playlist *playlist,
+ uint32_t version);
+
+/**
+ * Send the stored playlist to the client.
+ *
+ * @param client the client which requested the playlist
+ * @param name_utf8 the name of the stored playlist in UTF-8 encoding
+ * @param detail true if all details should be printed
+ * @return true on success, false if the playlist does not exist
+ */
+bool
+spl_print(Client *client, const char *name_utf8, bool detail,
+ GError **error_r);
+
+/**
+ * Send the playlist file to the client.
+ *
+ * @param client the client which requested the playlist
+ * @param uri the URI of the playlist file in UTF-8 encoding
+ * @param detail true if all details should be printed
+ * @return true on success, false if the playlist does not exist
+ */
+bool
+playlist_file_print(Client *client, const char *uri, bool detail);
+
+#endif
diff --git a/src/PlaylistQueue.cxx b/src/PlaylistQueue.cxx
new file mode 100644
index 000000000..c7b6c21fb
--- /dev/null
+++ b/src/PlaylistQueue.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 "PlaylistQueue.hxx"
+#include "PlaylistPlugin.hxx"
+#include "PlaylistAny.hxx"
+#include "PlaylistSong.hxx"
+#include "Playlist.hxx"
+#include "input_stream.h"
+#include "Song.hxx"
+
+enum playlist_result
+playlist_load_into_queue(const char *uri, struct playlist_provider *source,
+ unsigned start_index, unsigned end_index,
+ struct playlist *dest, struct player_control *pc,
+ bool secure)
+{
+ enum playlist_result result;
+ Song *song;
+ char *base_uri = uri != NULL ? g_path_get_dirname(uri) : NULL;
+
+ for (unsigned i = 0;
+ i < end_index && (song = playlist_plugin_read(source)) != NULL;
+ ++i) {
+ if (i < start_index) {
+ /* skip songs before the start index */
+ song->Free();
+ continue;
+ }
+
+ song = playlist_check_translate_song(song, base_uri, secure);
+ if (song == NULL)
+ continue;
+
+ result = dest->AppendSong(*pc, song);
+ song->Free();
+ if (result != PLAYLIST_RESULT_SUCCESS) {
+ g_free(base_uri);
+ return result;
+ }
+ }
+
+ g_free(base_uri);
+
+ return PLAYLIST_RESULT_SUCCESS;
+}
+
+enum playlist_result
+playlist_open_into_queue(const char *uri,
+ unsigned start_index, unsigned end_index,
+ struct playlist *dest, struct player_control *pc,
+ bool secure)
+{
+ Mutex mutex;
+ Cond cond;
+
+ struct input_stream *is;
+ struct playlist_provider *playlist =
+ playlist_open_any(uri, mutex, cond, &is);
+ if (playlist == NULL)
+ return PLAYLIST_RESULT_NO_SUCH_LIST;
+
+ enum playlist_result result =
+ playlist_load_into_queue(uri, playlist, start_index, end_index,
+ dest, pc, secure);
+ playlist_plugin_close(playlist);
+
+ if (is != NULL)
+ input_stream_close(is);
+
+ return result;
+}
diff --git a/src/PlaylistQueue.hxx b/src/PlaylistQueue.hxx
new file mode 100644
index 000000000..cda77c818
--- /dev/null
+++ b/src/PlaylistQueue.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.
+ */
+
+/*! \file
+ * \brief Glue between playlist plugin and the play queue
+ */
+
+#ifndef MPD_PLAYLIST_QUEUE_HXX
+#define MPD_PLAYLIST_QUEUE_HXX
+
+#include "playlist_error.h"
+
+struct playlist_provider;
+struct playlist;
+struct player_control;
+
+/**
+ * Loads the contents of a playlist and append it to the specified
+ * play queue.
+ *
+ * @param uri the URI of the playlist, used to resolve relative song
+ * URIs
+ * @param start_index the index of the first song
+ * @param end_index the index of the last song (excluding)
+ */
+enum playlist_result
+playlist_load_into_queue(const char *uri, struct playlist_provider *source,
+ unsigned start_index, unsigned end_index,
+ struct playlist *dest, struct player_control *pc,
+ bool secure);
+
+/**
+ * Opens a playlist with a playlist plugin and append to the specified
+ * play queue.
+ */
+enum playlist_result
+playlist_open_into_queue(const char *uri,
+ unsigned start_index, unsigned end_index,
+ struct playlist *dest, struct player_control *pc,
+ bool secure);
+
+#endif
+
diff --git a/src/PlaylistRegistry.cxx b/src/PlaylistRegistry.cxx
new file mode 100644
index 000000000..97681d99e
--- /dev/null
+++ b/src/PlaylistRegistry.cxx
@@ -0,0 +1,351 @@
+/*
+ * 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 "PlaylistRegistry.hxx"
+#include "PlaylistPlugin.hxx"
+#include "playlist/ExtM3uPlaylistPlugin.hxx"
+#include "playlist/M3uPlaylistPlugin.hxx"
+#include "playlist/XspfPlaylistPlugin.hxx"
+#include "playlist/LastFMPlaylistPlugin.hxx"
+#include "playlist/DespotifyPlaylistPlugin.hxx"
+#include "playlist/SoundCloudPlaylistPlugin.hxx"
+#include "playlist/PlsPlaylistPlugin.hxx"
+#include "playlist/AsxPlaylistPlugin.hxx"
+#include "playlist/RssPlaylistPlugin.hxx"
+#include "playlist/CuePlaylistPlugin.hxx"
+#include "playlist/EmbeddedCuePlaylistPlugin.hxx"
+#include "input_stream.h"
+#include "util/UriUtil.hxx"
+#include "util/StringUtil.hxx"
+#include "conf.h"
+#include "mpd_error.h"
+
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+
+const struct playlist_plugin *const playlist_plugins[] = {
+ &extm3u_playlist_plugin,
+ &m3u_playlist_plugin,
+ &xspf_playlist_plugin,
+ &pls_playlist_plugin,
+ &asx_playlist_plugin,
+ &rss_playlist_plugin,
+#ifdef ENABLE_DESPOTIFY
+ &despotify_playlist_plugin,
+#endif
+#ifdef ENABLE_LASTFM
+ &lastfm_playlist_plugin,
+#endif
+#ifdef ENABLE_SOUNDCLOUD
+ &soundcloud_playlist_plugin,
+#endif
+ &cue_playlist_plugin,
+ &embcue_playlist_plugin,
+ NULL
+};
+
+/** which plugins have been initialized successfully? */
+static bool playlist_plugins_enabled[G_N_ELEMENTS(playlist_plugins)];
+
+#define playlist_plugins_for_each_enabled(plugin) \
+ playlist_plugins_for_each(plugin) \
+ if (playlist_plugins_enabled[playlist_plugin_iterator - playlist_plugins])
+
+/**
+ * Find the "playlist" configuration block for the specified plugin.
+ *
+ * @param plugin_name the name of the playlist plugin
+ * @return the configuration block, or NULL if none was configured
+ */
+static const struct config_param *
+playlist_plugin_config(const char *plugin_name)
+{
+ const struct config_param *param = NULL;
+
+ assert(plugin_name != NULL);
+
+ while ((param = config_get_next_param(CONF_PLAYLIST_PLUGIN, param)) != NULL) {
+ const char *name = param->GetBlockValue("name");
+ if (name == NULL)
+ MPD_ERROR("playlist configuration without 'plugin' name in line %d",
+ param->line);
+
+ if (strcmp(name, plugin_name) == 0)
+ return param;
+ }
+
+ return NULL;
+}
+
+void
+playlist_list_global_init(void)
+{
+ const config_param empty;
+
+ for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) {
+ const struct playlist_plugin *plugin = playlist_plugins[i];
+ const struct config_param *param =
+ playlist_plugin_config(plugin->name);
+ if (param == nullptr)
+ param = &empty;
+ else if (!param->GetBlockValue("enabled", true))
+ /* the plugin is disabled in mpd.conf */
+ continue;
+
+ playlist_plugins_enabled[i] =
+ playlist_plugin_init(playlist_plugins[i], *param);
+ }
+}
+
+void
+playlist_list_global_finish(void)
+{
+ playlist_plugins_for_each_enabled(plugin)
+ playlist_plugin_finish(plugin);
+}
+
+static struct playlist_provider *
+playlist_list_open_uri_scheme(const char *uri, Mutex &mutex, Cond &cond,
+ bool *tried)
+{
+ char *scheme;
+ struct playlist_provider *playlist = NULL;
+
+ assert(uri != NULL);
+
+ scheme = g_uri_parse_scheme(uri);
+ if (scheme == NULL)
+ return NULL;
+
+ for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) {
+ const struct playlist_plugin *plugin = playlist_plugins[i];
+
+ assert(!tried[i]);
+
+ if (playlist_plugins_enabled[i] && plugin->open_uri != NULL &&
+ plugin->schemes != NULL &&
+ string_array_contains(plugin->schemes, scheme)) {
+ playlist = playlist_plugin_open_uri(plugin, uri,
+ mutex, cond);
+ if (playlist != NULL)
+ break;
+
+ tried[i] = true;
+ }
+ }
+
+ g_free(scheme);
+ return playlist;
+}
+
+static struct playlist_provider *
+playlist_list_open_uri_suffix(const char *uri, Mutex &mutex, Cond &cond,
+ const bool *tried)
+{
+ const char *suffix;
+ struct playlist_provider *playlist = NULL;
+
+ assert(uri != NULL);
+
+ suffix = uri_get_suffix(uri);
+ if (suffix == NULL)
+ return NULL;
+
+ for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) {
+ const struct playlist_plugin *plugin = playlist_plugins[i];
+
+ if (playlist_plugins_enabled[i] && !tried[i] &&
+ plugin->open_uri != NULL && plugin->suffixes != NULL &&
+ string_array_contains(plugin->suffixes, suffix)) {
+ playlist = playlist_plugin_open_uri(plugin, uri,
+ mutex, cond);
+ if (playlist != NULL)
+ break;
+ }
+ }
+
+ return playlist;
+}
+
+struct playlist_provider *
+playlist_list_open_uri(const char *uri, Mutex &mutex, Cond &cond)
+{
+ struct playlist_provider *playlist;
+ /** this array tracks which plugins have already been tried by
+ playlist_list_open_uri_scheme() */
+ bool tried[G_N_ELEMENTS(playlist_plugins) - 1];
+
+ assert(uri != NULL);
+
+ memset(tried, false, sizeof(tried));
+
+ playlist = playlist_list_open_uri_scheme(uri, mutex, cond, tried);
+ if (playlist == NULL)
+ playlist = playlist_list_open_uri_suffix(uri, mutex, cond,
+ tried);
+
+ return playlist;
+}
+
+static struct playlist_provider *
+playlist_list_open_stream_mime2(struct input_stream *is, const char *mime)
+{
+ struct playlist_provider *playlist;
+
+ assert(is != NULL);
+ assert(mime != NULL);
+
+ playlist_plugins_for_each_enabled(plugin) {
+ if (plugin->open_stream != NULL &&
+ plugin->mime_types != NULL &&
+ string_array_contains(plugin->mime_types, mime)) {
+ /* rewind the stream, so each plugin gets a
+ fresh start */
+ input_stream_seek(is, 0, SEEK_SET, NULL);
+
+ playlist = playlist_plugin_open_stream(plugin, is);
+ if (playlist != NULL)
+ return playlist;
+ }
+ }
+
+ return NULL;
+}
+
+static struct playlist_provider *
+playlist_list_open_stream_mime(struct input_stream *is, const char *full_mime)
+{
+ assert(full_mime != NULL);
+
+ const char *semicolon = strchr(full_mime, ';');
+ if (semicolon == NULL)
+ return playlist_list_open_stream_mime2(is, full_mime);
+
+ if (semicolon == full_mime)
+ return NULL;
+
+ /* probe only the portion before the semicolon*/
+ char *mime = g_strndup(full_mime, semicolon - full_mime);
+ struct playlist_provider *playlist =
+ playlist_list_open_stream_mime2(is, mime);
+ g_free(mime);
+ return playlist;
+}
+
+static struct playlist_provider *
+playlist_list_open_stream_suffix(struct input_stream *is, const char *suffix)
+{
+ struct playlist_provider *playlist;
+
+ assert(is != NULL);
+ assert(suffix != NULL);
+
+ playlist_plugins_for_each_enabled(plugin) {
+ if (plugin->open_stream != NULL &&
+ plugin->suffixes != NULL &&
+ string_array_contains(plugin->suffixes, suffix)) {
+ /* rewind the stream, so each plugin gets a
+ fresh start */
+ input_stream_seek(is, 0, SEEK_SET, NULL);
+
+ playlist = playlist_plugin_open_stream(plugin, is);
+ if (playlist != NULL)
+ return playlist;
+ }
+ }
+
+ return NULL;
+}
+
+struct playlist_provider *
+playlist_list_open_stream(struct input_stream *is, const char *uri)
+{
+ const char *suffix;
+ struct playlist_provider *playlist;
+
+ input_stream_lock_wait_ready(is);
+
+ const char *const mime = input_stream_get_mime_type(is);
+ if (mime != NULL) {
+ playlist = playlist_list_open_stream_mime(is, mime);
+ if (playlist != NULL)
+ return playlist;
+ }
+
+ suffix = uri != NULL ? uri_get_suffix(uri) : NULL;
+ if (suffix != NULL) {
+ playlist = playlist_list_open_stream_suffix(is, suffix);
+ if (playlist != NULL)
+ return playlist;
+ }
+
+ return NULL;
+}
+
+bool
+playlist_suffix_supported(const char *suffix)
+{
+ assert(suffix != NULL);
+
+ playlist_plugins_for_each_enabled(plugin) {
+ if (plugin->suffixes != NULL &&
+ string_array_contains(plugin->suffixes, suffix))
+ return true;
+ }
+
+ return false;
+}
+
+struct playlist_provider *
+playlist_list_open_path(const char *path_fs, Mutex &mutex, Cond &cond,
+ struct input_stream **is_r)
+{
+ GError *error = NULL;
+ const char *suffix;
+ struct input_stream *is;
+ struct playlist_provider *playlist;
+
+ assert(path_fs != NULL);
+
+ suffix = uri_get_suffix(path_fs);
+ if (suffix == NULL || !playlist_suffix_supported(suffix))
+ return NULL;
+
+ is = input_stream_open(path_fs, mutex, cond, &error);
+ if (is == NULL) {
+ if (error != NULL) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ }
+
+ return NULL;
+ }
+
+ input_stream_lock_wait_ready(is);
+
+ playlist = playlist_list_open_stream_suffix(is, suffix);
+ if (playlist != NULL)
+ *is_r = is;
+ else
+ input_stream_close(is);
+
+ return playlist;
+}
diff --git a/src/PlaylistRegistry.hxx b/src/PlaylistRegistry.hxx
new file mode 100644
index 000000000..7c34c1565
--- /dev/null
+++ b/src/PlaylistRegistry.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_PLAYLIST_REGISTRY_HXX
+#define MPD_PLAYLIST_REGISTRY_HXX
+
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+
+struct playlist_provider;
+struct input_stream;
+
+extern const struct playlist_plugin *const playlist_plugins[];
+
+#define playlist_plugins_for_each(plugin) \
+ for (const struct playlist_plugin *plugin, \
+ *const*playlist_plugin_iterator = &playlist_plugins[0]; \
+ (plugin = *playlist_plugin_iterator) != NULL; \
+ ++playlist_plugin_iterator)
+
+/**
+ * Initializes all playlist plugins.
+ */
+void
+playlist_list_global_init(void);
+
+/**
+ * Deinitializes all playlist plugins.
+ */
+void
+playlist_list_global_finish(void);
+
+/**
+ * Opens a playlist by its URI.
+ */
+struct playlist_provider *
+playlist_list_open_uri(const char *uri, Mutex &mutex, Cond &cond);
+
+/**
+ * Opens a playlist from an input stream.
+ *
+ * @param is an #input_stream object which is open and ready
+ * @param uri optional URI which was used to open the stream; may be
+ * used to select the appropriate playlist plugin
+ */
+struct playlist_provider *
+playlist_list_open_stream(struct input_stream *is, const char *uri);
+
+/**
+ * Determines if there is a playlist plugin which can handle the
+ * specified file name suffix.
+ */
+bool
+playlist_suffix_supported(const char *suffix);
+
+/**
+ * Opens a playlist from a local file.
+ *
+ * @param path_fs the path of the playlist file
+ * @param is_r on success, an input_stream object is returned here,
+ * which must be closed after the playlist_provider object is freed
+ * @return a playlist, or NULL on error
+ */
+struct playlist_provider *
+playlist_list_open_path(const char *path_fs, Mutex &mutex, Cond &cond,
+ struct input_stream **is_r);
+
+#endif
diff --git a/src/PlaylistSave.cxx b/src/PlaylistSave.cxx
new file mode 100644
index 000000000..b259b1f3d
--- /dev/null
+++ b/src/PlaylistSave.cxx
@@ -0,0 +1,135 @@
+/*
+ * 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 "PlaylistSave.hxx"
+#include "PlaylistFile.hxx"
+#include "Playlist.hxx"
+#include "Song.hxx"
+#include "Mapper.hxx"
+#include "Idle.hxx"
+#include "fs/Path.hxx"
+#include "fs/FileSystem.hxx"
+#include "util/UriUtil.hxx"
+
+#include <glib.h>
+
+void
+playlist_print_song(FILE *file, const Song *song)
+{
+ if (playlist_saveAbsolutePaths && song->IsInDatabase()) {
+ const Path path = map_song_fs(song);
+ if (!path.IsNull())
+ fprintf(file, "%s\n", path.c_str());
+ } else {
+ char *uri = song->GetURI();
+ const Path uri_fs = Path::FromUTF8(uri);
+ g_free(uri);
+
+ if (!uri_fs.IsNull())
+ fprintf(file, "%s\n", uri_fs.c_str());
+ }
+}
+
+void
+playlist_print_uri(FILE *file, const char *uri)
+{
+ Path path = playlist_saveAbsolutePaths && !uri_has_scheme(uri) &&
+ !g_path_is_absolute(uri)
+ ? map_uri_fs(uri)
+ : Path::FromUTF8(uri);
+
+ if (!path.IsNull())
+ fprintf(file, "%s\n", path.c_str());
+}
+
+enum playlist_result
+spl_save_queue(const char *name_utf8, const struct queue *queue)
+{
+ if (map_spl_path().IsNull())
+ return PLAYLIST_RESULT_DISABLED;
+
+ if (!spl_valid_name(name_utf8))
+ return PLAYLIST_RESULT_BAD_NAME;
+
+ const Path path_fs = map_spl_utf8_to_fs(name_utf8);
+ if (path_fs.IsNull())
+ return PLAYLIST_RESULT_BAD_NAME;
+
+ if (FileExists(path_fs))
+ return PLAYLIST_RESULT_LIST_EXISTS;
+
+ FILE *file = FOpen(path_fs, FOpenMode::WriteText);
+
+ if (file == NULL)
+ return PLAYLIST_RESULT_ERRNO;
+
+ for (unsigned i = 0; i < queue->GetLength(); i++)
+ playlist_print_song(file, queue->Get(i));
+
+ fclose(file);
+
+ idle_add(IDLE_STORED_PLAYLIST);
+ return PLAYLIST_RESULT_SUCCESS;
+}
+
+enum playlist_result
+spl_save_playlist(const char *name_utf8, const struct playlist *playlist)
+{
+ return spl_save_queue(name_utf8, &playlist->queue);
+}
+
+bool
+playlist_load_spl(struct playlist *playlist, struct player_control *pc,
+ const char *name_utf8,
+ unsigned start_index, unsigned end_index,
+ GError **error_r)
+{
+ GError *error = NULL;
+ PlaylistFileContents contents = LoadPlaylistFile(name_utf8, &error);
+ if (contents.empty() && error != nullptr) {
+ g_propagate_error(error_r, error);
+ return false;
+ }
+
+ if (end_index > contents.size())
+ end_index = contents.size();
+
+ for (unsigned i = start_index; i < end_index; ++i) {
+ const auto &uri_utf8 = contents[i];
+
+ if ((playlist->AppendURI(*pc, uri_utf8.c_str())) != PLAYLIST_RESULT_SUCCESS) {
+ /* for windows compatibility, convert slashes */
+ char *temp2 = g_strdup(uri_utf8.c_str());
+ char *p = temp2;
+ while (*p) {
+ if (*p == '\\')
+ *p = '/';
+ p++;
+ }
+
+ if (playlist->AppendURI(*pc, temp2) != PLAYLIST_RESULT_SUCCESS)
+ g_warning("can't add file \"%s\"", temp2);
+
+ g_free(temp2);
+ }
+ }
+
+ return true;
+}
diff --git a/src/PlaylistSave.hxx b/src/PlaylistSave.hxx
new file mode 100644
index 000000000..382df1b19
--- /dev/null
+++ b/src/PlaylistSave.hxx
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ */
+
+#ifndef MPD_PLAYLIST_SAVE_H
+#define MPD_PLAYLIST_SAVE_H
+
+#include "playlist_error.h"
+
+#include <stdio.h>
+
+struct Song;
+struct queue;
+struct playlist;
+struct player_control;
+
+void
+playlist_print_song(FILE *fp, const Song *song);
+
+void
+playlist_print_uri(FILE *fp, const char *uri);
+
+/**
+ * Saves a queue object into a stored playlist file.
+ */
+enum playlist_result
+spl_save_queue(const char *name_utf8, const struct queue *queue);
+
+/**
+ * Saves a playlist object into a stored playlist file.
+ */
+enum playlist_result
+spl_save_playlist(const char *name_utf8, const struct playlist *playlist);
+
+/**
+ * Loads a stored playlist file, and append all songs to the global
+ * playlist.
+ */
+bool
+playlist_load_spl(struct playlist *playlist, struct player_control *pc,
+ const char *name_utf8,
+ unsigned start_index, unsigned end_index,
+ GError **error_r);
+
+#endif
diff --git a/src/PlaylistSong.cxx b/src/PlaylistSong.cxx
new file mode 100644
index 000000000..5de1f5c8c
--- /dev/null
+++ b/src/PlaylistSong.cxx
@@ -0,0 +1,174 @@
+/*
+ * 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 "PlaylistSong.hxx"
+#include "Mapper.hxx"
+#include "DatabasePlugin.hxx"
+#include "DatabaseGlue.hxx"
+#include "ls.hxx"
+#include "Tag.hxx"
+#include "fs/Path.hxx"
+#include "util/UriUtil.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)
+{
+ dest->tag = base->tag != NULL
+ ? (add->tag != NULL
+ ? Tag::Merge(*base->tag, *add->tag)
+ : new Tag(*base->tag))
+ : (add->tag != NULL
+ ? new Tag(*add->tag)
+ : NULL);
+
+ dest->mtime = base->mtime;
+ dest->start_ms = add->start_ms;
+ dest->end_ms = add->end_ms;
+}
+
+static Song *
+apply_song_metadata(Song *dest, const Song *src)
+{
+ Song *tmp;
+
+ assert(dest != NULL);
+ assert(src != NULL);
+
+ if (src->tag == NULL && src->start_ms == 0 && src->end_ms == 0)
+ return dest;
+
+ if (dest->IsInDatabase()) {
+ const Path &path_fs = map_song_fs(dest);
+ if (path_fs.IsNull())
+ return dest;
+
+ std::string path_utf8 = path_fs.ToUTF8();
+ if (path_utf8.empty())
+ path_utf8 = path_fs.c_str();
+
+ tmp = Song::NewFile(path_utf8.c_str(), NULL);
+
+ merge_song_metadata(tmp, dest, src);
+ } else {
+ tmp = Song::NewFile(dest->uri, NULL);
+ merge_song_metadata(tmp, dest, src);
+ }
+
+ if (dest->tag != NULL && dest->tag->time > 0 &&
+ src->start_ms > 0 && src->end_ms == 0 &&
+ src->start_ms / 1000 < (unsigned)dest->tag->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;
+
+ dest->Free();
+ return tmp;
+}
+
+static Song *
+playlist_check_load_song(const Song *song, const char *uri, bool secure)
+{
+ Song *dest;
+
+ if (uri_has_scheme(uri)) {
+ dest = Song::NewRemote(uri);
+ } else if (g_path_is_absolute(uri) && secure) {
+ dest = Song::LoadFile(uri, nullptr);
+ if (dest == NULL)
+ return NULL;
+ } else {
+ const Database *db = GetDatabase(nullptr);
+ if (db == nullptr)
+ return nullptr;
+
+ Song *tmp = db->GetSong(uri, nullptr);
+ if (tmp == NULL)
+ /* not found in database */
+ return NULL;
+
+ dest = tmp->DupDetached();
+ db->ReturnSong(tmp);
+ }
+
+ return apply_song_metadata(dest, song);
+}
+
+Song *
+playlist_check_translate_song(Song *song, const char *base_uri,
+ bool secure)
+{
+ if (song->IsInDatabase())
+ /* already ok */
+ return song;
+
+ const char *uri = song->uri;
+
+ if (uri_has_scheme(uri)) {
+ if (uri_supported_scheme(uri))
+ /* valid remote song */
+ return song;
+ else {
+ /* unsupported remote song */
+ song->Free();
+ return NULL;
+ }
+ }
+
+ if (base_uri != NULL && strcmp(base_uri, ".") == 0)
+ /* g_path_get_dirname() returns "." when there is no
+ directory name in the given path; clear that now,
+ because it would break the database lookup
+ functions */
+ base_uri = NULL;
+
+ if (g_path_is_absolute(uri)) {
+ /* XXX fs_charset vs utf8? */
+ const char *suffix = map_to_relative_path(uri);
+ assert(suffix != NULL);
+
+ if (suffix != uri)
+ uri = suffix;
+ else if (!secure) {
+ /* local files must be relative to the music
+ directory when "secure" is enabled */
+ song->Free();
+ return NULL;
+ }
+
+ base_uri = NULL;
+ }
+
+ char *allocated = NULL;
+ if (base_uri != NULL)
+ uri = allocated = g_build_filename(base_uri, uri, NULL);
+
+ 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
new file mode 100644
index 000000000..68c99c4ec
--- /dev/null
+++ b/src/PlaylistSong.hxx
@@ -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.
+ */
+
+#ifndef MPD_PLAYLIST_SONG_HXX
+#define MPD_PLAYLIST_SONG_HXX
+
+struct Song;
+
+/**
+ * Verifies the song, returns NULL if it is unsafe. Translate the
+ * song to a new song object within the database, if it is a local
+ * file. The old song object is freed.
+ *
+ * @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,
+ bool secure);
+
+#endif
diff --git a/src/PlaylistState.cxx b/src/PlaylistState.cxx
new file mode 100644
index 000000000..d03de0a16
--- /dev/null
+++ b/src/PlaylistState.cxx
@@ -0,0 +1,229 @@
+/*
+ * 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.
+ */
+
+/*
+ * Saving and loading the playlist to/from the state file.
+ *
+ */
+
+#include "config.h"
+#include "PlaylistState.hxx"
+#include "Playlist.hxx"
+#include "QueueSave.hxx"
+#include "TextFile.hxx"
+#include "PlayerControl.hxx"
+#include "conf.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+#define PLAYLIST_STATE_FILE_STATE "state: "
+#define PLAYLIST_STATE_FILE_RANDOM "random: "
+#define PLAYLIST_STATE_FILE_REPEAT "repeat: "
+#define PLAYLIST_STATE_FILE_SINGLE "single: "
+#define PLAYLIST_STATE_FILE_CONSUME "consume: "
+#define PLAYLIST_STATE_FILE_CURRENT "current: "
+#define PLAYLIST_STATE_FILE_TIME "time: "
+#define PLAYLIST_STATE_FILE_CROSSFADE "crossfade: "
+#define PLAYLIST_STATE_FILE_MIXRAMPDB "mixrampdb: "
+#define PLAYLIST_STATE_FILE_MIXRAMPDELAY "mixrampdelay: "
+#define PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "playlist_begin"
+#define PLAYLIST_STATE_FILE_PLAYLIST_END "playlist_end"
+
+#define PLAYLIST_STATE_FILE_STATE_PLAY "play"
+#define PLAYLIST_STATE_FILE_STATE_PAUSE "pause"
+#define PLAYLIST_STATE_FILE_STATE_STOP "stop"
+
+#define PLAYLIST_BUFFER_SIZE 2*MPD_PATH_MAX
+
+void
+playlist_state_save(FILE *fp, const struct playlist *playlist,
+ struct player_control *pc)
+{
+ const auto player_status = pc->GetStatus();
+
+ fputs(PLAYLIST_STATE_FILE_STATE, fp);
+
+ if (playlist->playing) {
+ switch (player_status.state) {
+ case PLAYER_STATE_PAUSE:
+ fputs(PLAYLIST_STATE_FILE_STATE_PAUSE "\n", fp);
+ break;
+ default:
+ fputs(PLAYLIST_STATE_FILE_STATE_PLAY "\n", fp);
+ }
+ fprintf(fp, PLAYLIST_STATE_FILE_CURRENT "%i\n",
+ playlist->queue.OrderToPosition(playlist->current));
+ fprintf(fp, PLAYLIST_STATE_FILE_TIME "%i\n",
+ (int)player_status.elapsed_time);
+ } else {
+ fputs(PLAYLIST_STATE_FILE_STATE_STOP "\n", fp);
+
+ if (playlist->current >= 0)
+ fprintf(fp, PLAYLIST_STATE_FILE_CURRENT "%i\n",
+ playlist->queue.OrderToPosition(playlist->current));
+ }
+
+ fprintf(fp, PLAYLIST_STATE_FILE_RANDOM "%i\n", playlist->queue.random);
+ fprintf(fp, PLAYLIST_STATE_FILE_REPEAT "%i\n", playlist->queue.repeat);
+ fprintf(fp, PLAYLIST_STATE_FILE_SINGLE "%i\n", playlist->queue.single);
+ fprintf(fp, PLAYLIST_STATE_FILE_CONSUME "%i\n",
+ playlist->queue.consume);
+ fprintf(fp, PLAYLIST_STATE_FILE_CROSSFADE "%i\n",
+ (int)pc->GetCrossFade());
+ fprintf(fp, PLAYLIST_STATE_FILE_MIXRAMPDB "%f\n",
+ pc->GetMixRampDb());
+ fprintf(fp, PLAYLIST_STATE_FILE_MIXRAMPDELAY "%f\n",
+ pc->GetMixRampDelay());
+ fputs(PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "\n", fp);
+ queue_save(fp, &playlist->queue);
+ fputs(PLAYLIST_STATE_FILE_PLAYLIST_END "\n", fp);
+}
+
+static void
+playlist_state_load(TextFile &file, struct playlist *playlist)
+{
+ const char *line = file.ReadLine();
+ if (line == NULL) {
+ g_warning("No playlist in state file");
+ return;
+ }
+
+ while (!g_str_has_prefix(line, PLAYLIST_STATE_FILE_PLAYLIST_END)) {
+ queue_load_song(file, line, &playlist->queue);
+
+ line = file.ReadLine();
+ if (line == NULL) {
+ g_warning("'" PLAYLIST_STATE_FILE_PLAYLIST_END
+ "' not found in state file");
+ break;
+ }
+ }
+
+ playlist->queue.IncrementVersion();
+}
+
+bool
+playlist_state_restore(const char *line, TextFile &file,
+ struct playlist *playlist, struct player_control *pc)
+{
+ int current = -1;
+ int seek_time = 0;
+ enum player_state state = PLAYER_STATE_STOP;
+ bool random_mode = false;
+
+ if (!g_str_has_prefix(line, PLAYLIST_STATE_FILE_STATE))
+ return false;
+
+ line += sizeof(PLAYLIST_STATE_FILE_STATE) - 1;
+
+ if (strcmp(line, PLAYLIST_STATE_FILE_STATE_PLAY) == 0)
+ state = PLAYER_STATE_PLAY;
+ else if (strcmp(line, PLAYLIST_STATE_FILE_STATE_PAUSE) == 0)
+ state = PLAYER_STATE_PAUSE;
+
+ while ((line = file.ReadLine()) != NULL) {
+ if (g_str_has_prefix(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)) {
+ playlist->SetRepeat(*pc,
+ strcmp(&(line[strlen(PLAYLIST_STATE_FILE_REPEAT)]),
+ "1") == 0);
+ } else if (g_str_has_prefix(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)) {
+ playlist->SetConsume(strcmp(&(line[strlen(PLAYLIST_STATE_FILE_CONSUME)]),
+ "1") == 0);
+ } else if (g_str_has_prefix(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)) {
+ pc->SetMixRampDb(atof(line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDB)));
+ } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_MIXRAMPDELAY)) {
+ pc->SetMixRampDelay(atof(line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDELAY)));
+ } else if (g_str_has_prefix(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)) {
+ current = atoi(&(line
+ [strlen
+ (PLAYLIST_STATE_FILE_CURRENT)]));
+ } else if (g_str_has_prefix(line,
+ PLAYLIST_STATE_FILE_PLAYLIST_BEGIN)) {
+ playlist_state_load(file, playlist);
+ }
+ }
+
+ playlist->SetRandom(*pc, random_mode);
+
+ if (!playlist->queue.IsEmpty()) {
+ if (!playlist->queue.IsValidPosition(current))
+ current = 0;
+
+ if (state == PLAYER_STATE_PLAY &&
+ config_get_bool(CONF_RESTORE_PAUSED, false))
+ /* the user doesn't want MPD to auto-start
+ playback after startup; fall back to
+ "pause" */
+ state = PLAYER_STATE_PAUSE;
+
+ /* enable all devices for the first time; this must be
+ called here, after the audio output states were
+ restored, before playback begins */
+ if (state != PLAYER_STATE_STOP)
+ pc->UpdateAudio();
+
+ if (state == PLAYER_STATE_STOP /* && config_option */)
+ playlist->current = current;
+ else if (seek_time == 0)
+ playlist->PlayPosition(*pc, current);
+ else
+ playlist->SeekSongPosition(*pc, current, seek_time);
+
+ if (state == PLAYER_STATE_PAUSE)
+ pc->Pause();
+ }
+
+ return true;
+}
+
+unsigned
+playlist_state_get_hash(const struct playlist *playlist,
+ struct player_control *pc)
+{
+ const auto player_status = pc->GetStatus();
+
+ return playlist->queue.version ^
+ (player_status.state != PLAYER_STATE_STOP
+ ? ((int)player_status.elapsed_time << 8)
+ : 0) ^
+ (playlist->current >= 0
+ ? (playlist->queue.OrderToPosition(playlist->current) << 16)
+ : 0) ^
+ ((int)pc->GetCrossFade() << 20) ^
+ (player_status.state << 24) ^
+ (playlist->queue.random << 27) ^
+ (playlist->queue.repeat << 28) ^
+ (playlist->queue.single << 29) ^
+ (playlist->queue.consume << 30) ^
+ (playlist->queue.random << 31);
+}
diff --git a/src/PlaylistState.hxx b/src/PlaylistState.hxx
new file mode 100644
index 000000000..7e9944789
--- /dev/null
+++ b/src/PlaylistState.hxx
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+
+/*
+ * Saving and loading the playlist to/from the state file.
+ *
+ */
+
+#ifndef MPD_PLAYLIST_STATE_HXX
+#define MPD_PLAYLIST_STATE_HXX
+
+#include <stdio.h>
+
+struct playlist;
+struct player_control;
+class TextFile;
+
+void
+playlist_state_save(FILE *fp, const struct playlist *playlist,
+ struct player_control *pc);
+
+bool
+playlist_state_restore(const char *line, TextFile &file,
+ struct playlist *playlist, struct player_control *pc);
+
+/**
+ * Generates a hash number for the current state of the playlist and
+ * the playback options. This is used by timer_save_state_file() to
+ * determine whether the state has changed and the state file should
+ * be saved.
+ */
+unsigned
+playlist_state_get_hash(const struct playlist *playlist,
+ struct player_control *pc);
+
+#endif
diff --git a/src/PlaylistVector.cxx b/src/PlaylistVector.cxx
new file mode 100644
index 000000000..06c7b9ff0
--- /dev/null
+++ b/src/PlaylistVector.cxx
@@ -0,0 +1,68 @@
+/*
+ * 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 "PlaylistVector.hxx"
+#include "DatabaseLock.hxx"
+
+#include <algorithm>
+
+#include <assert.h>
+#include <string.h>
+#include <glib.h>
+
+PlaylistVector::iterator
+PlaylistVector::find(const char *name)
+{
+ assert(holding_db_lock());
+ assert(name != NULL);
+
+ return std::find_if(begin(), end(),
+ PlaylistInfo::CompareName(name));
+}
+
+bool
+PlaylistVector::UpdateOrInsert(PlaylistInfo &&pi)
+{
+ assert(holding_db_lock());
+
+ auto i = find(pi.name.c_str());
+ if (i != end()) {
+ if (pi.mtime == i->mtime)
+ return false;
+
+ i->mtime = pi.mtime;
+ } else
+ push_back(std::move(pi));
+
+ return true;
+}
+
+bool
+PlaylistVector::erase(const char *name)
+{
+ assert(holding_db_lock());
+
+ auto i = find(name);
+ if (i == end())
+ return false;
+
+ erase(i);
+ return true;
+}
diff --git a/src/PlaylistVector.hxx b/src/PlaylistVector.hxx
new file mode 100644
index 000000000..d10c90fda
--- /dev/null
+++ b/src/PlaylistVector.hxx
@@ -0,0 +1,56 @@
+/*
+ * 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_PLAYLIST_VECTOR_HXX
+#define MPD_PLAYLIST_VECTOR_HXX
+
+#include "PlaylistInfo.hxx"
+#include "gcc.h"
+
+#include <list>
+
+class PlaylistVector : protected std::list<PlaylistInfo> {
+protected:
+ /**
+ * Caller must lock the #db_mutex.
+ */
+ gcc_pure
+ iterator find(const char *name);
+
+public:
+ using std::list<PlaylistInfo>::empty;
+ using std::list<PlaylistInfo>::begin;
+ using std::list<PlaylistInfo>::end;
+ using std::list<PlaylistInfo>::push_back;
+ using std::list<PlaylistInfo>::erase;
+
+ /**
+ * Caller must lock the #db_mutex.
+ *
+ * @return true if the vector or one of its items was modified
+ */
+ bool UpdateOrInsert(PlaylistInfo &&pi);
+
+ /**
+ * Caller must lock the #db_mutex.
+ */
+ bool erase(const char *name);
+};
+
+#endif /* SONGVEC_H */
diff --git a/src/Queue.cxx b/src/Queue.cxx
new file mode 100644
index 000000000..6bb8175a1
--- /dev/null
+++ b/src/Queue.cxx
@@ -0,0 +1,499 @@
+/*
+ * 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 "Queue.hxx"
+#include "Song.hxx"
+
+#include <stdlib.h>
+
+queue::queue(unsigned _max_length)
+ :max_length(_max_length), length(0),
+ version(1),
+ items(new Item[max_length]),
+ order(new unsigned[max_length]),
+ id_table(max_length * HASH_MULT),
+ repeat(false),
+ single(false),
+ consume(false),
+ random(false)
+{
+}
+
+queue::~queue()
+{
+ Clear();
+
+ delete[] items;
+ delete[] order;
+}
+
+int
+queue::GetNextOrder(unsigned _order) const
+{
+ assert(_order < length);
+
+ if (single && repeat && !consume)
+ return _order;
+ else if (_order + 1 < length)
+ return _order + 1;
+ else if (repeat && (_order > 0 || !consume))
+ /* restart at first song */
+ return 0;
+ else
+ /* end of queue */
+ return -1;
+}
+
+void
+queue::IncrementVersion()
+{
+ static unsigned long max = ((uint32_t) 1 << 31) - 1;
+
+ version++;
+
+ if (version >= max) {
+ for (unsigned i = 0; i < length; i++)
+ items[i].version = 0;
+
+ version = 1;
+ }
+}
+
+void
+queue::ModifyAtOrder(unsigned _order)
+{
+ assert(_order < length);
+
+ unsigned position = order[_order];
+ items[position].version = version;
+
+ IncrementVersion();
+}
+
+void
+queue::ModifyAll()
+{
+ for (unsigned i = 0; i < length; i++)
+ items[i].version = version;
+
+ IncrementVersion();
+}
+
+unsigned
+queue::Append(Song *song, uint8_t priority)
+{
+ assert(!IsFull());
+
+ const unsigned position = length++;
+ const unsigned id = id_table.Insert(position);
+
+ auto &item = items[position];
+ item.song = song->DupDetached();
+ item.id = id;
+ item.version = version;
+ item.priority = priority;
+
+ order[position] = position;
+
+ return id;
+}
+
+void
+queue::SwapPositions(unsigned position1, unsigned position2)
+{
+ unsigned id1 = items[position1].id;
+ unsigned id2 = items[position2].id;
+
+ std::swap(items[position1], items[position2]);
+
+ items[position1].version = version;
+ items[position2].version = version;
+
+ id_table.Move(id1, position2);
+ id_table.Move(id2, position1);
+}
+
+void
+queue::MovePostion(unsigned from, unsigned to)
+{
+ const Item tmp = items[from];
+
+ /* move songs to one less in from->to */
+
+ for (unsigned i = from; i < to; i++)
+ MoveItemTo(i + 1, i);
+
+ /* move songs to one more in to->from */
+
+ for (unsigned i = from; i > to; i--)
+ MoveItemTo(i - 1, i);
+
+ /* put song at _to_ */
+
+ id_table.Move(tmp.id, to);
+ items[to] = tmp;
+ items[to].version = version;
+
+ /* now deal with order */
+
+ if (random) {
+ for (unsigned i = 0; i < length; i++) {
+ if (order[i] > from && order[i] <= to)
+ order[i]--;
+ else if (order[i] < from &&
+ order[i] >= to)
+ order[i]++;
+ else if (from == order[i])
+ order[i] = to;
+ }
+ }
+}
+
+void
+queue::MoveRange(unsigned start, unsigned end, unsigned to)
+{
+ Item tmp[end - start];
+ // Copy the original block [start,end-1]
+ for (unsigned i = start; i < end; i++)
+ tmp[i - start] = items[i];
+
+ // If to > start, we need to move to-start items to start, starting from end
+ for (unsigned i = end; i < end + to - start; i++)
+ MoveItemTo(i, start + i - end);
+
+ // If to < start, we need to move start-to items to newend (= end + to - start), starting from to
+ // This is the same as moving items from start-1 to to (decreasing), with start-1 going to end-1
+ // We have to iterate in this order to avoid writing over something we haven't yet moved
+ for (int i = start - 1; i >= int(to); i--)
+ MoveItemTo(i, i + end - start);
+
+ // Copy the original block back in, starting at to.
+ for (unsigned i = start; i< end; i++)
+ {
+ id_table.Move(tmp[i - start].id, to + i - start);
+ items[to + i - start] = tmp[i-start];
+ items[to + i - start].version = version;
+ }
+
+ if (random) {
+ // Update the positions in the queue.
+ // Note that the ranges for these cases are the same as the ranges of
+ // the loops above.
+ for (unsigned i = 0; i < length; i++) {
+ if (order[i] >= end && order[i] < to + end - start)
+ order[i] -= end - start;
+ else if (order[i] < start &&
+ order[i] >= to)
+ order[i] += end - start;
+ else if (start <= order[i] && order[i] < end)
+ order[i] += to - start;
+ }
+ }
+}
+
+void
+queue::MoveOrder(unsigned from_order, unsigned to_order)
+{
+ assert(from_order < length);
+ assert(to_order <= length);
+
+ const unsigned from_position = OrderToPosition(from_order);
+
+ if (from_order < to_order) {
+ for (unsigned i = from_order; i < to_order; ++i)
+ order[i] = order[i + 1];
+ } else {
+ for (unsigned i = from_order; i > to_order; --i)
+ order[i] = order[i - 1];
+ }
+
+ order[to_order] = from_position;
+}
+
+void
+queue::DeletePosition(unsigned position)
+{
+ assert(position < length);
+
+ Song *song = Get(position);
+ assert(!song->IsInDatabase() || song->IsDetached());
+ song->Free();
+
+ const unsigned id = PositionToId(position);
+ const unsigned _order = PositionToOrder(position);
+
+ --length;
+
+ /* release the song id */
+
+ id_table.Erase(id);
+
+ /* delete song from songs array */
+
+ for (unsigned i = position; i < length; i++)
+ MoveItemTo(i + 1, i);
+
+ /* delete the entry from the order array */
+
+ for (unsigned i = _order; i < length; i++)
+ order[i] = order[i + 1];
+
+ /* readjust values in the order array */
+
+ for (unsigned i = 0; i < length; i++)
+ if (order[i] > position)
+ --order[i];
+}
+
+void
+queue::Clear()
+{
+ for (unsigned i = 0; i < length; i++) {
+ Item *item = &items[i];
+
+ assert(!item->song->IsInDatabase() ||
+ item->song->IsDetached());
+ item->song->Free();
+
+ id_table.Erase(item->id);
+ }
+
+ length = 0;
+}
+
+static void
+queue_sort_order_by_priority(struct queue *queue, unsigned start, unsigned end)
+{
+ assert(queue != NULL);
+ assert(queue->random);
+ assert(start <= end);
+ assert(end <= queue->length);
+
+ auto cmp = [queue](unsigned a_pos, unsigned b_pos){
+ const queue::Item &a = queue->items[a_pos];
+ const queue::Item &b = queue->items[b_pos];
+
+ return a.priority > b.priority;
+ };
+
+ std::stable_sort(queue->order + start, queue->order + end, cmp);
+}
+
+void
+queue::ShuffleOrderRange(unsigned start, unsigned end)
+{
+ assert(random);
+ assert(start <= end);
+ assert(end <= length);
+
+ rand.AutoCreate();
+ std::shuffle(order + start, order + end, rand);
+}
+
+/**
+ * Sort the "order" of items by priority, and then shuffle each
+ * priority group.
+ */
+void
+queue::ShuffleOrderRangeWithPriority(unsigned start, unsigned end)
+{
+ assert(random);
+ assert(start <= end);
+ assert(end <= length);
+
+ if (start == end)
+ return;
+
+ /* first group the range by priority */
+ queue_sort_order_by_priority(this, start, end);
+
+ /* now shuffle each priority group */
+ unsigned group_start = start;
+ uint8_t group_priority = GetOrderPriority(start);
+
+ for (unsigned i = start + 1; i < end; ++i) {
+ const uint8_t priority = GetOrderPriority(i);
+ assert(priority <= group_priority);
+
+ if (priority != group_priority) {
+ /* start of a new group - shuffle the one that
+ has just ended */
+ ShuffleOrderRange(group_start, i);
+ group_start = i;
+ group_priority = priority;
+ }
+ }
+
+ /* shuffle the last group */
+ ShuffleOrderRange(group_start, end);
+}
+
+void
+queue::ShuffleOrder()
+{
+ ShuffleOrderRangeWithPriority(0, length);
+}
+
+void
+queue::ShuffleOrderFirst(unsigned start, unsigned end)
+{
+ rand.AutoCreate();
+
+ std::uniform_int_distribution<unsigned> distribution(start, end - 1);
+ SwapOrders(start, distribution(rand));
+}
+
+void
+queue::ShuffleOrderLast(unsigned start, unsigned end)
+{
+ rand.AutoCreate();
+
+ std::uniform_int_distribution<unsigned> distribution(start, end - 1);
+ SwapOrders(end - 1, distribution(rand));
+}
+
+void
+queue::ShuffleRange(unsigned start, unsigned end)
+{
+ assert(start <= end);
+ assert(end <= length);
+
+ rand.AutoCreate();
+
+ for (unsigned i = start; i < end; i++) {
+ std::uniform_int_distribution<unsigned> distribution(start,
+ end - 1);
+ unsigned ri = distribution(rand);
+ SwapPositions(i, ri);
+ }
+}
+
+unsigned
+queue::FindPriorityOrder(unsigned start_order, uint8_t priority,
+ unsigned exclude_order) const
+{
+ assert(random);
+ assert(start_order <= length);
+
+ for (unsigned i = start_order; i < length; ++i) {
+ const unsigned position = OrderToPosition(i);
+ const Item *item = &items[position];
+ if (item->priority <= priority && i != exclude_order)
+ return i;
+ }
+
+ return length;
+}
+
+unsigned
+queue::CountSamePriority(unsigned start_order, uint8_t priority) const
+{
+ assert(random);
+ assert(start_order <= length);
+
+ for (unsigned i = start_order; i < length; ++i) {
+ const unsigned position = OrderToPosition(i);
+ const Item *item = &items[position];
+ if (item->priority != priority)
+ return i - start_order;
+ }
+
+ return length - start_order;
+}
+
+bool
+queue::SetPriority(unsigned position, uint8_t priority, int after_order)
+{
+ assert(position < length);
+
+ Item *item = &items[position];
+ uint8_t old_priority = item->priority;
+ if (old_priority == priority)
+ return false;
+
+ item->version = version;
+ item->priority = priority;
+
+ if (!random)
+ /* don't reorder if not in random mode */
+ return true;
+
+ unsigned _order = PositionToOrder(position);
+ if (after_order >= 0) {
+ if (_order == (unsigned)after_order)
+ /* don't reorder the current song */
+ return true;
+
+ if (_order < (unsigned)after_order) {
+ /* the specified song has been played already
+ - enqueue it only if its priority has just
+ become bigger than the current one's */
+
+ const unsigned after_position =
+ OrderToPosition(after_order);
+ const Item *after_item =
+ &items[after_position];
+ if (old_priority > after_item->priority ||
+ priority <= after_item->priority)
+ /* priority hasn't become bigger */
+ return true;
+ }
+ }
+
+ /* move the item to the beginning of the priority group (or
+ create a new priority group) */
+
+ const unsigned before_order =
+ FindPriorityOrder(after_order + 1, priority, _order);
+ const unsigned new_order = before_order > _order
+ ? before_order - 1
+ : before_order;
+ MoveOrder(_order, new_order);
+
+ /* shuffle the song within that priority group */
+
+ const unsigned priority_count = CountSamePriority(new_order, priority);
+ assert(priority_count >= 1);
+ ShuffleOrderFirst(new_order, new_order + priority_count);
+
+ return true;
+}
+
+bool
+queue::SetPriorityRange(unsigned start_position, unsigned end_position,
+ uint8_t priority, int after_order)
+{
+ assert(start_position <= end_position);
+ assert(end_position <= length);
+
+ bool modified = false;
+ int after_position = after_order >= 0
+ ? (int)OrderToPosition(after_order)
+ : -1;
+ for (unsigned i = start_position; i < end_position; ++i) {
+ after_order = after_position >= 0
+ ? (int)PositionToOrder(after_position)
+ : -1;
+
+ modified |= SetPriority(i, priority, after_order);
+ }
+
+ return modified;
+}
diff --git a/src/Queue.hxx b/src/Queue.hxx
new file mode 100644
index 000000000..d8eeb271e
--- /dev/null
+++ b/src/Queue.hxx
@@ -0,0 +1,371 @@
+/*
+ * 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_QUEUE_HXX
+#define MPD_QUEUE_HXX
+
+#include "gcc.h"
+#include "IdTable.hxx"
+#include "util/LazyRandomEngine.hxx"
+
+#include <algorithm>
+
+#include <assert.h>
+#include <stdint.h>
+
+struct Song;
+
+/**
+ * A queue of songs. This is the backend of the playlist: it contains
+ * an ordered list of songs.
+ *
+ * Songs can be addressed in three possible ways:
+ *
+ * - the position in the queue
+ * - the unique id (which stays the same, regardless of moves)
+ * - the order number (which only differs from "position" in random mode)
+ */
+struct queue {
+ /**
+ * reserve max_length * HASH_MULT elements in the id
+ * number space
+ */
+ static constexpr unsigned HASH_MULT = 4;
+
+ /**
+ * One element of the queue: basically a song plus some queue specific
+ * information attached.
+ */
+ struct Item {
+ Song *song;
+
+ /** the unique id of this item in the queue */
+ unsigned id;
+
+ /** when was this item last changed? */
+ uint32_t version;
+
+ /**
+ * The priority of this item, between 0 and 255. High
+ * priority value means that this song gets played first in
+ * "random" mode.
+ */
+ uint8_t priority;
+ };
+
+ /** configured maximum length of the queue */
+ unsigned max_length;
+
+ /** number of songs in the queue */
+ unsigned length;
+
+ /** the current version number */
+ uint32_t version;
+
+ /** all songs in "position" order */
+ Item *items;
+
+ /** map order numbers to positions */
+ unsigned *order;
+
+ /** map song ids to positions */
+ IdTable id_table;
+
+ /** repeat playback when the end of the queue has been
+ reached? */
+ bool repeat;
+
+ /** play only current song. */
+ bool single;
+
+ /** remove each played files. */
+ bool consume;
+
+ /** play back songs in random order? */
+ bool random;
+
+ /** random number generator for shuffle and random mode */
+ LazyRandomEngine rand;
+
+ queue(unsigned max_length);
+
+ /**
+ * Deinitializes a queue object. It does not free the queue
+ * pointer itself.
+ */
+ ~queue();
+
+ queue(const queue &other) = delete;
+ queue &operator=(const queue &other) = delete;
+
+ unsigned GetLength() const {
+ assert(length <= max_length);
+
+ return length;
+ }
+
+ /**
+ * Determine if the queue is empty, i.e. there are no songs.
+ */
+ bool IsEmpty() const {
+ return length == 0;
+ }
+
+ /**
+ * Determine if the maximum number of songs has been reached.
+ */
+ bool IsFull() const {
+ assert(length <= max_length);
+
+ return length >= max_length;
+ }
+
+ /**
+ * Is that a valid position number?
+ */
+ bool IsValidPosition(unsigned position) const {
+ return position < length;
+ }
+
+ /**
+ * Is that a valid order number?
+ */
+ bool IsValidOrder(unsigned _order) const {
+ return _order < length;
+ }
+
+ int IdToPosition(unsigned id) const {
+ return id_table.IdToPosition(id);
+ }
+
+ int PositionToId(unsigned position) const
+ {
+ assert(position < length);
+
+ return items[position].id;
+ }
+
+ gcc_pure
+ unsigned OrderToPosition(unsigned _order) const {
+ assert(_order < length);
+
+ return order[_order];
+ }
+
+ gcc_pure
+ unsigned PositionToOrder(unsigned position) const {
+ assert(position < length);
+
+ for (unsigned i = 0;; ++i) {
+ assert(i < length);
+
+ if (order[i] == position)
+ return i;
+ }
+ }
+
+ gcc_pure
+ uint8_t GetPriorityAtPosition(unsigned position) const {
+ assert(position < length);
+
+ return items[position].priority;
+ }
+
+ const Item &GetOrderItem(unsigned i) const {
+ assert(IsValidOrder(i));
+
+ return items[OrderToPosition(i)];
+ }
+
+ uint8_t GetOrderPriority(unsigned i) const {
+ return GetOrderItem(i).priority;
+ }
+
+ /**
+ * Returns the song at the specified position.
+ */
+ Song *Get(unsigned position) const {
+ assert(position < length);
+
+ return items[position].song;
+ }
+
+ /**
+ * Returns the song at the specified order number.
+ */
+ Song *GetOrder(unsigned _order) const {
+ return Get(OrderToPosition(_order));
+ }
+
+ /**
+ * Is the song at the specified position newer than the specified
+ * version?
+ */
+ bool IsNewerAtPosition(unsigned position, uint32_t _version) const {
+ assert(position < length);
+
+ return _version > version ||
+ items[position].version >= _version ||
+ items[position].version == 0;
+ }
+
+ /**
+ * Returns the order number following the specified one. This takes
+ * end of queue and "repeat" mode into account.
+ *
+ * @return the next order number, or -1 to stop playback
+ */
+ gcc_pure
+ int GetNextOrder(unsigned order) const;
+
+ /**
+ * Increments the queue's version number. This handles integer
+ * overflow well.
+ */
+ void IncrementVersion();
+
+ /**
+ * Marks the specified song as "modified" and increments the version
+ * number.
+ */
+ void ModifyAtOrder(unsigned order);
+
+ /**
+ * Marks all songs as "modified" and increments the version number.
+ */
+ void ModifyAll();
+
+ /**
+ * Appends a song to the queue and returns its position. Prior to
+ * that, the caller must check if the queue is already full.
+ *
+ * If a song is not in the database (determined by
+ * Song::IsInDatabase()), it is freed when removed from the
+ * queue.
+ *
+ * @param priority the priority of this new queue item
+ */
+ unsigned Append(Song *song, uint8_t priority);
+
+ /**
+ * Swaps two songs, addressed by their position.
+ */
+ void SwapPositions(unsigned position1, unsigned position2);
+
+ /**
+ * Swaps two songs, addressed by their order number.
+ */
+ void SwapOrders(unsigned order1, unsigned order2) {
+ std::swap(order[order1], order[order2]);
+ }
+
+ /**
+ * Moves a song to a new position.
+ */
+ void MovePostion(unsigned from, unsigned to);
+
+ /**
+ * Moves a range of songs to a new position.
+ */
+ void MoveRange(unsigned start, unsigned end, unsigned to);
+
+ /**
+ * Removes a song from the playlist.
+ */
+ void DeletePosition(unsigned position);
+
+ /**
+ * Removes all songs from the playlist.
+ */
+ void Clear();
+
+ /**
+ * Initializes the "order" array, and restores "normal" order.
+ */
+ void RestoreOrder() {
+ for (unsigned i = 0; i < length; ++i)
+ order[i] = i;
+ }
+
+ /**
+ * Shuffle the order of items in the specified range, ignoring
+ * their priorities.
+ */
+ void ShuffleOrderRange(unsigned start, unsigned end);
+
+ /**
+ * Shuffle the order of items in the specified range, taking their
+ * priorities into account.
+ */
+ void ShuffleOrderRangeWithPriority(unsigned start, unsigned end);
+
+ /**
+ * Shuffles the virtual order of songs, but does not move them
+ * physically. This is used in random mode.
+ */
+ void ShuffleOrder();
+
+ void ShuffleOrderFirst(unsigned start, unsigned end);
+
+ /**
+ * Shuffles the virtual order of the last song in the specified
+ * (order) range. This is used in random mode after a song has been
+ * appended by queue_append().
+ */
+ void ShuffleOrderLast(unsigned start, unsigned end);
+
+ /**
+ * Shuffles a (position) range in the queue. The songs are physically
+ * shuffled, not by using the "order" mapping.
+ */
+ void ShuffleRange(unsigned start, unsigned end);
+
+ bool SetPriority(unsigned position, uint8_t priority, int after_order);
+
+ bool SetPriorityRange(unsigned start_position, unsigned end_position,
+ uint8_t priority, int after_order);
+
+private:
+ /**
+ * Moves a song to a new position in the "order" list.
+ */
+ void MoveOrder(unsigned from_order, unsigned to_order);
+
+ void MoveItemTo(unsigned from, unsigned to) {
+ unsigned from_id = items[from].id;
+
+ items[to] = items[from];
+ items[to].version = version;
+ id_table.Move(from_id, to);
+ }
+
+ /**
+ * Find the first item that has this specified priority or
+ * higher.
+ */
+ gcc_pure
+ unsigned FindPriorityOrder(unsigned start_order, uint8_t priority,
+ unsigned exclude_order) const;
+
+ gcc_pure
+ unsigned CountSamePriority(unsigned start_order,
+ uint8_t priority) const;
+};
+
+#endif
diff --git a/src/QueueCommands.cxx b/src/QueueCommands.cxx
new file mode 100644
index 000000000..210b1501a
--- /dev/null
+++ b/src/QueueCommands.cxx
@@ -0,0 +1,387 @@
+/*
+ * 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 "QueueCommands.hxx"
+#include "CommandError.hxx"
+#include "DatabaseQueue.hxx"
+#include "SongFilter.hxx"
+#include "DatabaseSelection.hxx"
+#include "Playlist.hxx"
+#include "PlaylistPrint.hxx"
+#include "ClientFile.hxx"
+#include "ClientInternal.hxx"
+#include "Partition.hxx"
+#include "protocol/ArgParser.hxx"
+#include "protocol/Result.hxx"
+#include "ls.hxx"
+#include "util/UriUtil.hxx"
+#include "fs/Path.hxx"
+
+#include <string.h>
+
+enum command_return
+handle_add(Client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ char *uri = argv[1];
+ enum playlist_result result;
+
+ if (strncmp(uri, "file:///", 8) == 0) {
+ const char *path_utf8 = uri + 7;
+ const Path path_fs = Path::FromUTF8(path_utf8);
+
+ if (path_fs.IsNull()) {
+ command_error(client, ACK_ERROR_NO_EXIST,
+ "unsupported file name");
+ return COMMAND_RETURN_ERROR;
+ }
+
+ GError *error = NULL;
+ if (!client_allow_file(client, path_fs, &error))
+ return print_error(client, error);
+
+ result = client->partition.AppendFile(path_utf8);
+ return print_playlist_result(client, result);
+ }
+
+ if (uri_has_scheme(uri)) {
+ if (!uri_supported_scheme(uri)) {
+ command_error(client, ACK_ERROR_NO_EXIST,
+ "unsupported URI scheme");
+ return COMMAND_RETURN_ERROR;
+ }
+
+ result = client->partition.AppendURI(uri);
+ return print_playlist_result(client, result);
+ }
+
+ const DatabaseSelection selection(uri, true);
+ GError *error = NULL;
+ return AddFromDatabase(client->partition, selection, &error)
+ ? COMMAND_RETURN_OK
+ : print_error(client, error);
+}
+
+enum command_return
+handle_addid(Client *client, int argc, char *argv[])
+{
+ char *uri = argv[1];
+ unsigned added_id;
+ enum playlist_result result;
+
+ if (strncmp(uri, "file:///", 8) == 0) {
+ const char *path_utf8 = uri + 7;
+ const Path path_fs = Path::FromUTF8(path_utf8);
+
+ if (path_fs.IsNull()) {
+ command_error(client, ACK_ERROR_NO_EXIST,
+ "unsupported file name");
+ return COMMAND_RETURN_ERROR;
+ }
+
+ GError *error = NULL;
+ if (!client_allow_file(client, path_fs, &error))
+ return print_error(client, error);
+
+ result = client->partition.AppendFile(path_utf8, &added_id);
+ } else {
+ if (uri_has_scheme(uri) && !uri_supported_scheme(uri)) {
+ command_error(client, ACK_ERROR_NO_EXIST,
+ "unsupported URI scheme");
+ return COMMAND_RETURN_ERROR;
+ }
+
+ result = client->partition.AppendURI(uri, &added_id);
+ }
+
+ if (result != PLAYLIST_RESULT_SUCCESS)
+ return print_playlist_result(client, result);
+
+ if (argc == 3) {
+ unsigned to;
+ if (!check_unsigned(client, &to, argv[2]))
+ return COMMAND_RETURN_ERROR;
+ result = client->partition.MoveId(added_id, to);
+ if (result != PLAYLIST_RESULT_SUCCESS) {
+ enum command_return ret =
+ print_playlist_result(client, result);
+ client->partition.DeleteId(added_id);
+ return ret;
+ }
+ }
+
+ client_printf(client, "Id: %u\n", added_id);
+ return COMMAND_RETURN_OK;
+}
+
+enum command_return
+handle_delete(Client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ unsigned start, end;
+
+ if (!check_range(client, &start, &end, argv[1]))
+ return COMMAND_RETURN_ERROR;
+
+ enum playlist_result result = client->partition.DeleteRange(start, end);
+ return print_playlist_result(client, result);
+}
+
+enum command_return
+handle_deleteid(Client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ unsigned id;
+
+ if (!check_unsigned(client, &id, argv[1]))
+ return COMMAND_RETURN_ERROR;
+
+ enum playlist_result result = client->partition.DeleteId(id);
+ return print_playlist_result(client, result);
+}
+
+enum command_return
+handle_playlist(Client *client,
+ G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
+{
+ playlist_print_uris(client, &client->playlist);
+ return COMMAND_RETURN_OK;
+}
+
+enum command_return
+handle_shuffle(G_GNUC_UNUSED Client *client,
+ G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
+{
+ unsigned start = 0, end = client->playlist.queue.GetLength();
+ if (argc == 2 && !check_range(client, &start, &end, argv[1]))
+ return COMMAND_RETURN_ERROR;
+
+ client->partition.Shuffle(start, end);
+ return COMMAND_RETURN_OK;
+}
+
+enum command_return
+handle_clear(G_GNUC_UNUSED Client *client,
+ G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
+{
+ client->partition.ClearQueue();
+ return COMMAND_RETURN_OK;
+}
+
+enum command_return
+handle_plchanges(Client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ uint32_t version;
+
+ if (!check_uint32(client, &version, argv[1]))
+ return COMMAND_RETURN_ERROR;
+
+ playlist_print_changes_info(client, &client->playlist, version);
+ return COMMAND_RETURN_OK;
+}
+
+enum command_return
+handle_plchangesposid(Client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ uint32_t version;
+
+ if (!check_uint32(client, &version, argv[1]))
+ return COMMAND_RETURN_ERROR;
+
+ playlist_print_changes_position(client, &client->playlist, version);
+ return COMMAND_RETURN_OK;
+}
+
+enum command_return
+handle_playlistinfo(Client *client, int argc, char *argv[])
+{
+ unsigned start = 0, end = G_MAXUINT;
+ bool ret;
+
+ if (argc == 2 && !check_range(client, &start, &end, argv[1]))
+ return COMMAND_RETURN_ERROR;
+
+ ret = playlist_print_info(client, &client->playlist, start, end);
+ if (!ret)
+ return print_playlist_result(client,
+ PLAYLIST_RESULT_BAD_RANGE);
+
+ return COMMAND_RETURN_OK;
+}
+
+enum command_return
+handle_playlistid(Client *client, int argc, char *argv[])
+{
+ if (argc >= 2) {
+ unsigned id;
+ if (!check_unsigned(client, &id, argv[1]))
+ return COMMAND_RETURN_ERROR;
+
+ bool ret = playlist_print_id(client, &client->playlist, id);
+ if (!ret)
+ return print_playlist_result(client,
+ PLAYLIST_RESULT_NO_SUCH_SONG);
+ } else {
+ playlist_print_info(client, &client->playlist, 0, G_MAXUINT);
+ }
+
+ return COMMAND_RETURN_OK;
+}
+
+static enum command_return
+handle_playlist_match(Client *client, int argc, char *argv[],
+ bool fold_case)
+{
+ SongFilter filter;
+ if (!filter.Parse(argc - 1, argv + 1, fold_case)) {
+ command_error(client, ACK_ERROR_ARG, "incorrect arguments");
+ return COMMAND_RETURN_ERROR;
+ }
+
+ playlist_print_find(client, &client->playlist, filter);
+ return COMMAND_RETURN_OK;
+}
+
+enum command_return
+handle_playlistfind(Client *client, int argc, char *argv[])
+{
+ return handle_playlist_match(client, argc, argv, false);
+}
+
+enum command_return
+handle_playlistsearch(Client *client, int argc, char *argv[])
+{
+ return handle_playlist_match(client, argc, argv, true);
+}
+
+enum command_return
+handle_prio(Client *client, int argc, char *argv[])
+{
+ unsigned priority;
+
+ if (!check_unsigned(client, &priority, argv[1]))
+ return COMMAND_RETURN_ERROR;
+
+ if (priority > 0xff) {
+ command_error(client, ACK_ERROR_ARG,
+ "Priority out of range: %s", argv[1]);
+ return COMMAND_RETURN_ERROR;
+ }
+
+ for (int i = 2; i < argc; ++i) {
+ unsigned start_position, end_position;
+ if (!check_range(client, &start_position, &end_position,
+ argv[i]))
+ return COMMAND_RETURN_ERROR;
+
+ enum playlist_result result =
+ client->partition.SetPriorityRange(start_position,
+ end_position,
+ priority);
+ if (result != PLAYLIST_RESULT_SUCCESS)
+ return print_playlist_result(client, result);
+ }
+
+ return COMMAND_RETURN_OK;
+}
+
+enum command_return
+handle_prioid(Client *client, int argc, char *argv[])
+{
+ unsigned priority;
+
+ if (!check_unsigned(client, &priority, argv[1]))
+ return COMMAND_RETURN_ERROR;
+
+ if (priority > 0xff) {
+ command_error(client, ACK_ERROR_ARG,
+ "Priority out of range: %s", argv[1]);
+ return COMMAND_RETURN_ERROR;
+ }
+
+ for (int i = 2; i < argc; ++i) {
+ unsigned song_id;
+ if (!check_unsigned(client, &song_id, argv[i]))
+ return COMMAND_RETURN_ERROR;
+
+ enum playlist_result result =
+ client->partition.SetPriorityId(song_id, priority);
+ if (result != PLAYLIST_RESULT_SUCCESS)
+ return print_playlist_result(client, result);
+ }
+
+ return COMMAND_RETURN_OK;
+}
+
+enum command_return
+handle_move(Client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ unsigned start, end;
+ int to;
+
+ if (!check_range(client, &start, &end, argv[1]))
+ return COMMAND_RETURN_ERROR;
+ if (!check_int(client, &to, argv[2]))
+ return COMMAND_RETURN_ERROR;
+
+ enum playlist_result result =
+ client->partition.MoveRange(start, end, to);
+ return print_playlist_result(client, result);
+}
+
+enum command_return
+handle_moveid(Client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ unsigned id;
+ int to;
+
+ if (!check_unsigned(client, &id, argv[1]))
+ return COMMAND_RETURN_ERROR;
+ if (!check_int(client, &to, argv[2]))
+ return COMMAND_RETURN_ERROR;
+ enum playlist_result result = client->partition.MoveId(id, to);
+ return print_playlist_result(client, result);
+}
+
+enum command_return
+handle_swap(Client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ unsigned song1, song2;
+
+ if (!check_unsigned(client, &song1, argv[1]))
+ return COMMAND_RETURN_ERROR;
+ if (!check_unsigned(client, &song2, argv[2]))
+ return COMMAND_RETURN_ERROR;
+
+ enum playlist_result result =
+ client->partition.SwapPositions(song1, song2);
+ return print_playlist_result(client, result);
+}
+
+enum command_return
+handle_swapid(Client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ unsigned id1, id2;
+
+ if (!check_unsigned(client, &id1, argv[1]))
+ return COMMAND_RETURN_ERROR;
+ if (!check_unsigned(client, &id2, argv[2]))
+ return COMMAND_RETURN_ERROR;
+
+ enum playlist_result result = client->partition.SwapIds(id1, id2);
+ return print_playlist_result(client, result);
+}
diff --git a/src/QueueCommands.hxx b/src/QueueCommands.hxx
new file mode 100644
index 000000000..97b61e212
--- /dev/null
+++ b/src/QueueCommands.hxx
@@ -0,0 +1,84 @@
+/*
+ * 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.
+ */
+
+#ifndef MPD_QUEUE_COMMANDS_HXX
+#define MPD_QUEUE_COMMANDS_HXX
+
+#include "command.h"
+
+class Client;
+
+enum command_return
+handle_add(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_addid(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_delete(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_deleteid(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_playlist(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_shuffle(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_clear(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_plchanges(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_plchangesposid(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_playlistinfo(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_playlistid(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_playlistfind(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_playlistsearch(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_prio(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_prioid(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_move(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_moveid(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_swap(Client *client, int argc, char *argv[]);
+
+enum command_return
+handle_swapid(Client *client, int argc, char *argv[]);
+
+#endif
diff --git a/src/QueuePrint.cxx b/src/QueuePrint.cxx
new file mode 100644
index 000000000..d8d94d3b0
--- /dev/null
+++ b/src/QueuePrint.cxx
@@ -0,0 +1,107 @@
+/*
+ * 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 "QueuePrint.hxx"
+#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.
+ *
+ * @param client the client which has requested information
+ * @param start the index of the first song (including)
+ * @param end the index of the last song (excluding)
+ */
+static void
+queue_print_song_info(Client *client, const struct queue *queue,
+ unsigned position)
+{
+ song_print_info(client, queue->Get(position));
+ client_printf(client, "Pos: %u\nId: %u\n",
+ position, queue->PositionToId(position));
+
+ uint8_t priority = queue->GetPriorityAtPosition(position);
+ if (priority != 0)
+ client_printf(client, "Prio: %u\n", priority);
+}
+
+void
+queue_print_info(Client *client, const struct queue *queue,
+ unsigned start, unsigned end)
+{
+ assert(start <= end);
+ assert(end <= queue->GetLength());
+
+ for (unsigned i = start; i < end; ++i)
+ queue_print_song_info(client, queue, i);
+}
+
+void
+queue_print_uris(Client *client, const struct queue *queue,
+ unsigned start, unsigned end)
+{
+ assert(start <= end);
+ assert(end <= queue->GetLength());
+
+ for (unsigned i = start; i < end; ++i) {
+ client_printf(client, "%i:", i);
+ song_print_uri(client, queue->Get(i));
+ }
+}
+
+void
+queue_print_changes_info(Client *client, const struct queue *queue,
+ uint32_t version)
+{
+ for (unsigned i = 0; i < queue->GetLength(); i++) {
+ if (queue->IsNewerAtPosition(i, version))
+ queue_print_song_info(client, queue, i);
+ }
+}
+
+void
+queue_print_changes_position(Client *client, const struct queue *queue,
+ uint32_t version)
+{
+ for (unsigned i = 0; i < queue->GetLength(); i++)
+ if (queue->IsNewerAtPosition(i, version))
+ client_printf(client, "cpos: %i\nId: %i\n",
+ i, queue->PositionToId(i));
+}
+
+void
+queue_find(Client *client, const struct queue *queue,
+ const SongFilter &filter)
+{
+ for (unsigned i = 0; i < queue->GetLength(); i++) {
+ const Song *song = queue->Get(i);
+
+ if (filter.Match(*song))
+ queue_print_song_info(client, queue, i);
+ }
+}
diff --git a/src/QueuePrint.hxx b/src/QueuePrint.hxx
new file mode 100644
index 000000000..6b3a29fb6
--- /dev/null
+++ b/src/QueuePrint.hxx
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+
+/*
+ * This library sends information about songs in the queue to the
+ * client.
+ */
+
+#ifndef MPD_QUEUE_PRINT_HXX
+#define MPD_QUEUE_PRINT_HXX
+
+#include <stdint.h>
+
+struct queue;
+class SongFilter;
+class Client;
+
+void
+queue_print_info(Client *client, const struct queue *queue,
+ unsigned start, unsigned end);
+
+void
+queue_print_uris(Client *client, const struct queue *queue,
+ unsigned start, unsigned end);
+
+void
+queue_print_changes_info(Client *client, const struct queue *queue,
+ uint32_t version);
+
+void
+queue_print_changes_position(Client *client, const struct queue *queue,
+ uint32_t version);
+
+void
+queue_find(Client *client, const struct queue *queue,
+ const SongFilter &filter);
+
+#endif
diff --git a/src/QueueSave.cxx b/src/QueueSave.cxx
new file mode 100644
index 000000000..fd00009b1
--- /dev/null
+++ b/src/QueueSave.cxx
@@ -0,0 +1,127 @@
+/*
+ * 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 "QueueSave.hxx"
+#include "Playlist.hxx"
+#include "Song.hxx"
+#include "SongSave.hxx"
+#include "DatabasePlugin.hxx"
+#include "DatabaseGlue.hxx"
+#include "TextFile.hxx"
+#include "util/UriUtil.hxx"
+
+#include <stdlib.h>
+
+#define PRIO_LABEL "Prio: "
+
+static void
+queue_save_database_song(FILE *fp, int idx, const Song *song)
+{
+ char *uri = song->GetURI();
+
+ fprintf(fp, "%i:%s\n", idx, uri);
+ g_free(uri);
+}
+
+static void
+queue_save_full_song(FILE *fp, const Song *song)
+{
+ song_save(fp, song);
+}
+
+static void
+queue_save_song(FILE *fp, int idx, const Song *song)
+{
+ if (song->IsInDatabase())
+ queue_save_database_song(fp, idx, song);
+ else
+ queue_save_full_song(fp, song);
+}
+
+void
+queue_save(FILE *fp, const struct queue *queue)
+{
+ for (unsigned i = 0; i < queue->GetLength(); i++) {
+ uint8_t prio = queue->GetPriorityAtPosition(i);
+ if (prio != 0)
+ fprintf(fp, PRIO_LABEL "%u\n", prio);
+
+ queue_save_song(fp, i, queue->Get(i));
+ }
+}
+
+void
+queue_load_song(TextFile &file, const char *line, queue *queue)
+{
+ if (queue->IsFull())
+ return;
+
+ uint8_t priority = 0;
+ if (g_str_has_prefix(line, PRIO_LABEL)) {
+ priority = strtoul(line + sizeof(PRIO_LABEL) - 1, NULL, 10);
+
+ line = file.ReadLine();
+ if (line == NULL)
+ return;
+ }
+
+ const Database *db = nullptr;
+ Song *song;
+
+ if (g_str_has_prefix(line, SONG_BEGIN)) {
+ const char *uri = line + sizeof(SONG_BEGIN) - 1;
+ if (!uri_has_scheme(uri) && !g_path_is_absolute(uri))
+ return;
+
+ GError *error = NULL;
+ song = song_load(file, NULL, uri, &error);
+ if (song == NULL) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return;
+ }
+ } else {
+ char *endptr;
+ long ret = strtol(line, &endptr, 10);
+ if (ret < 0 || *endptr != ':' || endptr[1] == 0) {
+ g_warning("Malformed playlist line in state file");
+ return;
+ }
+
+ const char *uri = endptr + 1;
+
+ if (uri_has_scheme(uri)) {
+ song = Song::NewRemote(uri);
+ } else {
+ db = GetDatabase(nullptr);
+ if (db == nullptr)
+ return;
+
+ song = db->GetSong(uri, nullptr);
+ if (song == nullptr)
+ return;
+ }
+ }
+
+ queue->Append(song, priority);
+
+ if (db != nullptr)
+ db->ReturnSong(song);
+}
diff --git a/src/QueueSave.hxx b/src/QueueSave.hxx
new file mode 100644
index 000000000..25d7804fb
--- /dev/null
+++ b/src/QueueSave.hxx
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+/*
+ * This library saves the queue into the state file, and also loads it
+ * back into memory.
+ */
+
+#ifndef MPD_QUEUE_SAVE_HXX
+#define MPD_QUEUE_SAVE_HXX
+
+#include <stdio.h>
+
+struct queue;
+class TextFile;
+
+void
+queue_save(FILE *fp, const struct queue *queue);
+
+/**
+ * Loads one song from the state file and appends it to the queue.
+ */
+void
+queue_load_song(TextFile &file, const char *line, queue *queue);
+
+#endif
diff --git a/src/ReplayGainConfig.cxx b/src/ReplayGainConfig.cxx
new file mode 100644
index 000000000..ea5054bae
--- /dev/null
+++ b/src/ReplayGainConfig.cxx
@@ -0,0 +1,144 @@
+/*
+ * 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 "replay_gain_config.h"
+#include "Idle.hxx"
+#include "conf.h"
+#include "Playlist.hxx"
+#include "mpd_error.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+enum replay_gain_mode replay_gain_mode = REPLAY_GAIN_OFF;
+
+static constexpr bool DEFAULT_REPLAYGAIN_LIMIT = true;
+
+float replay_gain_preamp = 1.0;
+float replay_gain_missing_preamp = 1.0;
+bool replay_gain_limit = DEFAULT_REPLAYGAIN_LIMIT;
+
+const char *
+replay_gain_get_mode_string(void)
+{
+ switch (replay_gain_mode) {
+ case REPLAY_GAIN_AUTO:
+ return "auto";
+
+ case REPLAY_GAIN_OFF:
+ return "off";
+
+ case REPLAY_GAIN_TRACK:
+ return "track";
+
+ case REPLAY_GAIN_ALBUM:
+ return "album";
+ }
+
+ assert(false);
+ gcc_unreachable();
+}
+
+bool
+replay_gain_set_mode_string(const char *p)
+{
+ assert(p != NULL);
+
+ if (strcmp(p, "off") == 0)
+ replay_gain_mode = REPLAY_GAIN_OFF;
+ else if (strcmp(p, "track") == 0)
+ replay_gain_mode = REPLAY_GAIN_TRACK;
+ else if (strcmp(p, "album") == 0)
+ replay_gain_mode = REPLAY_GAIN_ALBUM;
+ else if (strcmp(p, "auto") == 0)
+ replay_gain_mode = REPLAY_GAIN_AUTO;
+ else
+ return false;
+
+ idle_add(IDLE_OPTIONS);
+
+ return true;
+}
+
+void replay_gain_global_init(void)
+{
+ const struct config_param *param = config_get_param(CONF_REPLAYGAIN);
+
+ if (param != NULL && !replay_gain_set_mode_string(param->value)) {
+ MPD_ERROR("replaygain value \"%s\" at line %i is invalid\n",
+ param->value, param->line);
+ }
+
+ param = config_get_param(CONF_REPLAYGAIN_PREAMP);
+
+ if (param) {
+ char *test;
+ float f = strtod(param->value, &test);
+
+ if (*test != '\0') {
+ MPD_ERROR("Replaygain preamp \"%s\" is not a number at "
+ "line %i\n", param->value, param->line);
+ }
+
+ if (f < -15 || f > 15) {
+ MPD_ERROR("Replaygain preamp \"%s\" is not between -15 and"
+ "15 at line %i\n", param->value, param->line);
+ }
+
+ replay_gain_preamp = pow(10, f / 20.0);
+ }
+
+ param = config_get_param(CONF_REPLAYGAIN_MISSING_PREAMP);
+
+ if (param) {
+ char *test;
+ float f = strtod(param->value, &test);
+
+ if (*test != '\0') {
+ MPD_ERROR("Replaygain missing preamp \"%s\" is not a number at "
+ "line %i\n", param->value, param->line);
+ }
+
+ if (f < -15 || f > 15) {
+ MPD_ERROR("Replaygain missing preamp \"%s\" is not between -15 and"
+ "15 at line %i\n", param->value, param->line);
+ }
+
+ replay_gain_missing_preamp = pow(10, f / 20.0);
+ }
+
+ replay_gain_limit = config_get_bool(CONF_REPLAYGAIN_LIMIT, DEFAULT_REPLAYGAIN_LIMIT);
+}
+
+enum replay_gain_mode
+replay_gain_get_real_mode(bool random_mode)
+{
+ enum replay_gain_mode rgm;
+
+ rgm = replay_gain_mode;
+
+ if (rgm == REPLAY_GAIN_AUTO)
+ rgm = random_mode ? REPLAY_GAIN_TRACK : REPLAY_GAIN_ALBUM;
+
+ return rgm;
+}
diff --git a/src/ReplayGainInfo.cxx b/src/ReplayGainInfo.cxx
new file mode 100644
index 000000000..b9d1b82c6
--- /dev/null
+++ b/src/ReplayGainInfo.cxx
@@ -0,0 +1,48 @@
+/*
+ * 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 "replay_gain_info.h"
+
+float
+replay_gain_tuple_scale(const struct replay_gain_tuple *tuple, float preamp, float missing_preamp, bool peak_limit)
+{
+ float scale;
+
+ if (replay_gain_tuple_defined(tuple)) {
+ scale = pow(10.0, tuple->gain / 20.0);
+ scale *= preamp;
+ if (scale > 15.0)
+ scale = 15.0;
+
+ if (peak_limit && scale * tuple->peak > 1.0)
+ scale = 1.0 / tuple->peak;
+ } else
+ scale = missing_preamp;
+
+ return scale;
+}
+
+void
+replay_gain_info_complete(struct replay_gain_info *info)
+{
+ if (!replay_gain_tuple_defined(&info->tuples[REPLAY_GAIN_ALBUM]))
+ info->tuples[REPLAY_GAIN_ALBUM] =
+ info->tuples[REPLAY_GAIN_TRACK];
+}
diff --git a/src/SignalHandlers.cxx b/src/SignalHandlers.cxx
new file mode 100644
index 000000000..d438eb703
--- /dev/null
+++ b/src/SignalHandlers.cxx
@@ -0,0 +1,81 @@
+/*
+ * 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.
+ */
+
+#include "config.h"
+#include "SignalHandlers.hxx"
+
+#ifndef WIN32
+
+#include "Log.hxx"
+#include "Main.hxx"
+#include "event/Loop.hxx"
+#include "GlobalEvents.hxx"
+#include "mpd_error.h"
+
+#include <glib.h>
+
+#include <signal.h>
+#include <errno.h>
+#include <string.h>
+
+static void exit_signal_handler(G_GNUC_UNUSED int signum)
+{
+ GlobalEvents::Emit(GlobalEvents::SHUTDOWN);
+}
+
+static void reload_signal_handler(G_GNUC_UNUSED int signum)
+{
+ GlobalEvents::Emit(GlobalEvents::RELOAD);
+}
+
+static void
+x_sigaction(int signum, const struct sigaction *act)
+{
+ if (sigaction(signum, act, NULL) < 0)
+ MPD_ERROR("sigaction() failed: %s", strerror(errno));
+}
+
+static void
+handle_reload_event(void)
+{
+ g_debug("got SIGHUP, reopening log files");
+ cycle_log_files();
+}
+
+#endif
+
+void initSigHandlers(void)
+{
+#ifndef WIN32
+ struct sigaction sa;
+
+ sa.sa_flags = 0;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_handler = SIG_IGN;
+ x_sigaction(SIGPIPE, &sa);
+
+ sa.sa_handler = exit_signal_handler;
+ x_sigaction(SIGINT, &sa);
+ x_sigaction(SIGTERM, &sa);
+
+ GlobalEvents::Register(GlobalEvents::RELOAD, handle_reload_event);
+ sa.sa_handler = reload_signal_handler;
+ x_sigaction(SIGHUP, &sa);
+#endif
+}
diff --git a/src/SignalHandlers.hxx b/src/SignalHandlers.hxx
new file mode 100644
index 000000000..99f347fb0
--- /dev/null
+++ b/src/SignalHandlers.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_SIGNAL_HANDLERS_HXX
+#define MPD_SIGNAL_HANDLERS_HXX
+
+void initSigHandlers(void);
+
+#endif
diff --git a/src/SocketError.hxx b/src/SocketError.hxx
new file mode 100644
index 000000000..53a0c1d8f
--- /dev/null
+++ b/src/SocketError.hxx
@@ -0,0 +1,156 @@
+/*
+ * 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_SOCKET_ERROR_HXX
+#define MPD_SOCKET_ERROR_HXX
+
+#include "gcc.h"
+
+#include <glib.h>
+
+#ifdef WIN32
+#include <winsock2.h>
+typedef DWORD socket_error_t;
+#else
+#include <errno.h>
+typedef int socket_error_t;
+#endif
+
+/**
+ * A GQuark for GError for socket I/O errors. The code is an errno
+ * value (or WSAGetLastError() on Windows).
+ */
+gcc_const
+static inline GQuark
+SocketErrorQuark(void)
+{
+ return g_quark_from_static_string("socket");
+}
+
+gcc_pure
+static inline socket_error_t
+GetSocketError()
+{
+#ifdef WIN32
+ return WSAGetLastError();
+#else
+ return errno;
+#endif
+}
+
+gcc_const
+static inline bool
+IsSocketErrorAgain(socket_error_t code)
+{
+#ifdef WIN32
+ return code == WSAEINPROGRESS;
+#else
+ return code == EAGAIN;
+#endif
+}
+
+gcc_const
+static inline bool
+IsSocketErrorInterruped(socket_error_t code)
+{
+#ifdef WIN32
+ return code == WSAEINTR;
+#else
+ return code == EINTR;
+#endif
+}
+
+gcc_const
+static inline bool
+IsSocketErrorClosed(socket_error_t code)
+{
+#ifdef WIN32
+ return code == WSAECONNRESET;
+#else
+ return code == EPIPE || code == ECONNRESET;
+#endif
+}
+
+/**
+ * Helper class that formats a socket error message into a
+ * human-readable string. On Windows, a buffer is necessary for this,
+ * and this class hosts the buffer.
+ */
+class SocketErrorMessage {
+#ifdef WIN32
+ char msg[256];
+#else
+ const char *const msg;
+#endif
+
+public:
+#ifdef WIN32
+ explicit SocketErrorMessage(socket_error_t code=GetSocketError()) {
+ DWORD nbytes = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS |
+ FORMAT_MESSAGE_MAX_WIDTH_MASK,
+ NULL, code, 0,
+ (LPSTR)msg, sizeof(msg), NULL);
+ if (nbytes == 0)
+ strcpy(msg, "Unknown error");
+ }
+#else
+ explicit SocketErrorMessage(socket_error_t code=GetSocketError())
+ :msg(g_strerror(code)) {}
+#endif
+
+ operator const char *() const {
+ return msg;
+ }
+};
+
+static inline void
+SetSocketError(GError **error_r, socket_error_t code)
+{
+#ifdef WIN32
+ if (error_r == NULL)
+ return;
+#endif
+
+ const SocketErrorMessage msg(code);
+ g_set_error_literal(error_r, SocketErrorQuark(), code, msg);
+}
+
+static inline void
+SetSocketError(GError **error_r)
+{
+ SetSocketError(error_r, GetSocketError());
+}
+
+gcc_malloc
+static inline GError *
+NewSocketError(socket_error_t code)
+{
+ const SocketErrorMessage msg(code);
+ return g_error_new_literal(SocketErrorQuark(), code, msg);
+}
+
+gcc_malloc
+static inline GError *
+NewSocketError()
+{
+ return NewSocketError(GetSocketError());
+}
+
+#endif
diff --git a/src/SocketUtil.cxx b/src/SocketUtil.cxx
new file mode 100644
index 000000000..dd7eb7dd2
--- /dev/null
+++ b/src/SocketUtil.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 "SocketUtil.hxx"
+#include "SocketError.hxx"
+#include "fd_util.h"
+
+#include <glib.h>
+
+#include <unistd.h>
+
+#ifndef G_OS_WIN32
+#include <sys/socket.h>
+#else /* G_OS_WIN32 */
+#include <ws2tcpip.h>
+#include <winsock.h>
+#endif /* G_OS_WIN32 */
+
+#ifdef HAVE_IPV6
+#include <string.h>
+#endif
+
+int
+socket_bind_listen(int domain, int type, int protocol,
+ const struct sockaddr *address, size_t address_length,
+ int backlog,
+ GError **error_r)
+{
+ int fd, ret;
+ const int reuse = 1;
+
+ fd = socket_cloexec_nonblock(domain, type, protocol);
+ if (fd < 0) {
+ SetSocketError(error_r);
+ g_prefix_error(error_r, "Failed to create socket: ");
+ return -1;
+ }
+
+ ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
+ (const char *) &reuse, sizeof(reuse));
+ if (ret < 0) {
+ SetSocketError(error_r);
+ g_prefix_error(error_r, "setsockopt() failed: ");
+ close_socket(fd);
+ return -1;
+ }
+
+ ret = bind(fd, address, address_length);
+ if (ret < 0) {
+ SetSocketError(error_r);
+ close_socket(fd);
+ return -1;
+ }
+
+ ret = listen(fd, backlog);
+ if (ret < 0) {
+ SetSocketError(error_r);
+ g_prefix_error(error_r, "listen() failed: ");
+ close_socket(fd);
+ return -1;
+ }
+
+#ifdef HAVE_STRUCT_UCRED
+ setsockopt(fd, SOL_SOCKET, SO_PASSCRED,
+ (const char *) &reuse, sizeof(reuse));
+#endif
+
+ return fd;
+}
+
+int
+socket_keepalive(int fd)
+{
+ const int reuse = 1;
+
+ return setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE,
+ (const char *)&reuse, sizeof(reuse));
+}
diff --git a/src/SocketUtil.hxx b/src/SocketUtil.hxx
new file mode 100644
index 000000000..3d0be78c4
--- /dev/null
+++ b/src/SocketUtil.hxx
@@ -0,0 +1,58 @@
+/*
+ * 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.
+ */
+
+/*
+ * This library provides easy helper functions for working with
+ * sockets.
+ *
+ */
+
+#ifndef MPD_SOCKET_UTIL_HXX
+#define MPD_SOCKET_UTIL_HXX
+
+#include "gerror.h"
+
+#include <stddef.h>
+
+struct sockaddr;
+
+/**
+ * Creates a socket listening on the specified address. This is a
+ * shortcut for socket(), bind() and listen().
+ *
+ * @param domain the socket domain, e.g. PF_INET6
+ * @param type the socket type, e.g. SOCK_STREAM
+ * @param protocol the protocol, usually 0 to let the kernel choose
+ * @param address the address to listen on
+ * @param address_length the size of #address
+ * @param backlog the backlog parameter for the listen() system call
+ * @param error location to store the error occurring, or NULL to
+ * ignore errors
+ * @return the socket file descriptor or -1 on error
+ */
+int
+socket_bind_listen(int domain, int type, int protocol,
+ const struct sockaddr *address, size_t address_length,
+ int backlog,
+ GError **error);
+
+int
+socket_keepalive(int fd);
+
+#endif
diff --git a/src/Song.cxx b/src/Song.cxx
new file mode 100644
index 000000000..023d52071
--- /dev/null
+++ b/src/Song.cxx
@@ -0,0 +1,175 @@
+/*
+ * 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.
+ */
+
+#include "config.h"
+#include "Song.hxx"
+#include "Directory.hxx"
+#include "Tag.hxx"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+
+Directory detached_root;
+
+static Song *
+song_alloc(const char *uri, Directory *parent)
+{
+ size_t uri_length;
+
+ assert(uri);
+ uri_length = strlen(uri);
+ assert(uri_length);
+
+ Song *song = (Song *)
+ g_malloc(sizeof(*song) - sizeof(song->uri) + uri_length + 1);
+
+ song->tag = nullptr;
+ memcpy(song->uri, uri, uri_length + 1);
+ song->parent = parent;
+ song->mtime = 0;
+ song->start_ms = song->end_ms = 0;
+
+ return song;
+}
+
+Song *
+Song::NewRemote(const char *uri)
+{
+ return song_alloc(uri, nullptr);
+}
+
+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()) {
+ char *new_uri = GetURI();
+ song = NewDetached(new_uri);
+ g_free(new_uri);
+ } 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);
+}
+
+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
+song_equals(const Song *a, const Song *b)
+{
+ assert(a != nullptr);
+ assert(b != nullptr);
+
+ 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" */
+ char *au = a->GetURI();
+ char *bu = b->GetURI();
+ const bool result = strcmp(au, bu) == 0;
+ g_free(bu);
+ g_free(au);
+ return result;
+ }
+
+ return directory_is_same(a->parent, b->parent) &&
+ strcmp(a->uri, b->uri) == 0;
+}
+
+char *
+Song::GetURI() const
+{
+ assert(*uri);
+
+ if (!IsInDatabase() || parent->IsRoot())
+ return g_strdup(uri);
+ else
+ return g_strconcat(parent->GetPath(),
+ "/", uri, nullptr);
+}
+
+double
+Song::GetDuration() const
+{
+ if (end_ms > 0)
+ return (end_ms - start_ms) / 1000.0;
+
+ if (tag == nullptr)
+ return 0;
+
+ return tag->time - start_ms / 1000.0;
+}
diff --git a/src/Song.hxx b/src/Song.hxx
new file mode 100644
index 000000000..c1122f43b
--- /dev/null
+++ b/src/Song.hxx
@@ -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.
+ */
+
+#ifndef MPD_SONG_HXX
+#define MPD_SONG_HXX
+
+#include "util/list.h"
+#include "gcc.h"
+
+#include <assert.h>
+#include <sys/time.h>
+
+#define SONG_FILE "file: "
+#define SONG_TIME "Time: "
+
+struct Tag;
+
+/**
+ * A dummy #directory instance that is used for "detached" song
+ * copies.
+ */
+extern struct Directory detached_root;
+
+struct Song {
+ /**
+ * Pointers to the siblings of this directory within the
+ * parent directory. It is unused (undefined) if this song is
+ * not in the database.
+ *
+ * This attribute is protected with the global #db_mutex.
+ * Read access in the update thread does not need protection.
+ */
+ struct list_head siblings;
+
+ Tag *tag;
+ Directory *parent;
+ 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;
+
+ char uri[sizeof(int)];
+
+ /** allocate a new song with a remote URL */
+ gcc_malloc
+ static Song *NewRemote(const char *uri);
+
+ /** allocate a new song with a local file name */
+ gcc_malloc
+ static Song *NewFile(const char *path_utf8, Directory *parent);
+
+ /**
+ * allocate a new song structure with a local file name and attempt to
+ * load its metadata. If all decoder plugin fail to read its meta
+ * data, nullptr is returned.
+ */
+ gcc_malloc
+ static Song *LoadFile(const char *path_utf8, Directory *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;
+ }
+
+ bool UpdateFile();
+ bool UpdateFileInArchive();
+
+ /**
+ * Returns the URI of the song in UTF-8 encoding, including its
+ * location within the music directory.
+ *
+ * The return value is allocated on the heap, and must be freed by the
+ * caller.
+ */
+ gcc_malloc
+ char *GetURI() const;
+
+ gcc_pure
+ double GetDuration() const;
+};
+
+/**
+ * Returns true if both objects refer to the same physical song.
+ */
+gcc_pure
+bool
+song_equals(const Song *a, const Song *b);
+
+#endif
diff --git a/src/SongFilter.cxx b/src/SongFilter.cxx
new file mode 100644
index 000000000..c928c793d
--- /dev/null
+++ b/src/SongFilter.cxx
@@ -0,0 +1,167 @@
+/*
+ * 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 "SongFilter.hxx"
+#include "Song.hxx"
+#include "Tag.hxx"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+
+#define LOCATE_TAG_FILE_KEY "file"
+#define LOCATE_TAG_FILE_KEY_OLD "filename"
+#define LOCATE_TAG_ANY_KEY "any"
+
+unsigned
+locate_parse_type(const char *str)
+{
+ if (0 == g_ascii_strcasecmp(str, LOCATE_TAG_FILE_KEY) ||
+ 0 == g_ascii_strcasecmp(str, LOCATE_TAG_FILE_KEY_OLD))
+ return LOCATE_TAG_FILE_TYPE;
+
+ if (0 == g_ascii_strcasecmp(str, LOCATE_TAG_ANY_KEY))
+ return LOCATE_TAG_ANY_TYPE;
+
+ return tag_name_parse_i(str);
+}
+
+SongFilter::Item::Item(unsigned _tag, const char *_value, bool _fold_case)
+ :tag(_tag), fold_case(_fold_case),
+ value(fold_case
+ ? g_utf8_casefold(_value, -1)
+ : g_strdup(_value))
+{
+}
+
+SongFilter::Item::~Item()
+{
+ g_free(value);
+}
+
+bool
+SongFilter::Item::StringMatch(const char *s) const
+{
+ assert(value != nullptr);
+ assert(s != nullptr);
+
+ if (fold_case) {
+ char *p = g_utf8_casefold(s, -1);
+ const bool result = strstr(p, value) != NULL;
+ g_free(p);
+ return result;
+ } else {
+ return strcmp(s, value) == 0;
+ }
+}
+
+bool
+SongFilter::Item::Match(const TagItem &item) const
+{
+ return (tag == LOCATE_TAG_ANY_TYPE || (unsigned)item.type == tag) &&
+ StringMatch(item.value);
+}
+
+bool
+SongFilter::Item::Match(const Tag &_tag) const
+{
+ bool visited_types[TAG_NUM_OF_ITEM_TYPES];
+ std::fill(visited_types, visited_types + TAG_NUM_OF_ITEM_TYPES, false);
+
+ for (unsigned i = 0; i < _tag.num_items; i++) {
+ visited_types[_tag.items[i]->type] = true;
+
+ if (Match(*_tag.items[i]))
+ return true;
+ }
+
+ /** If the search critieron was not visited during the sweep
+ * through the song's tag, it means this field is absent from
+ * the tag or empty. Thus, if the searched string is also
+ * empty (first char is a \0), then it's a match as well and
+ * we should return true.
+ */
+ if (*value == 0 && tag < TAG_NUM_OF_ITEM_TYPES &&
+ !visited_types[tag])
+ return true;
+
+ return false;
+}
+
+bool
+SongFilter::Item::Match(const Song &song) const
+{
+ if (tag == LOCATE_TAG_FILE_TYPE || tag == LOCATE_TAG_ANY_TYPE) {
+ char *uri = song.GetURI();
+ const bool result = StringMatch(uri);
+ g_free(uri);
+
+ if (result || tag == LOCATE_TAG_FILE_TYPE)
+ return result;
+ }
+
+ return song.tag != NULL && Match(*song.tag);
+}
+
+SongFilter::SongFilter(unsigned tag, const char *value, bool fold_case)
+{
+ items.push_back(Item(tag, value, fold_case));
+}
+
+SongFilter::~SongFilter()
+{
+ /* this destructor exists here just so it won't get inlined */
+}
+
+bool
+SongFilter::Parse(const char *tag_string, const char *value, bool fold_case)
+{
+ unsigned tag = locate_parse_type(tag_string);
+ if (tag == TAG_NUM_OF_ITEM_TYPES)
+ return false;
+
+ items.push_back(Item(tag, value, fold_case));
+ return true;
+}
+
+bool
+SongFilter::Parse(unsigned argc, char *argv[], bool fold_case)
+{
+ if (argc == 0 || argc % 2 != 0)
+ return false;
+
+ for (unsigned i = 0; i < argc; i += 2)
+ if (!Parse(argv[i], argv[i + 1], fold_case))
+ return false;
+
+ return true;
+}
+
+bool
+SongFilter::Match(const Song &song) const
+{
+ for (const auto &i : items)
+ if (!i.Match(song))
+ return false;
+
+ return true;
+}
diff --git a/src/SongFilter.hxx b/src/SongFilter.hxx
new file mode 100644
index 000000000..88378d710
--- /dev/null
+++ b/src/SongFilter.hxx
@@ -0,0 +1,107 @@
+/*
+ * 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.
+ */
+
+#ifndef MPD_SONG_FILTER_HXX
+#define MPD_SONG_FILTER_HXX
+
+#include "gcc.h"
+
+#include <list>
+
+#include <stdint.h>
+
+#define LOCATE_TAG_FILE_TYPE TAG_NUM_OF_ITEM_TYPES+10
+#define LOCATE_TAG_ANY_TYPE TAG_NUM_OF_ITEM_TYPES+20
+
+struct Tag;
+struct TagItem;
+struct Song;
+
+class SongFilter {
+ class Item {
+ uint8_t tag;
+
+ bool fold_case;
+
+ char *value;
+
+ public:
+ gcc_nonnull(3)
+ Item(unsigned tag, const char *value, bool fold_case=false);
+
+ Item(const Item &other) = delete;
+
+ Item(Item &&other)
+ :tag(other.tag), fold_case(other.fold_case),
+ value(other.value) {
+ other.value = nullptr;
+ }
+
+ ~Item();
+
+ Item &operator=(const Item &other) = delete;
+
+ unsigned GetTag() const {
+ return tag;
+ }
+
+ gcc_pure gcc_nonnull(2)
+ bool StringMatch(const char *s) const;
+
+ gcc_pure
+ bool Match(const TagItem &tag_item) const;
+
+ gcc_pure
+ bool Match(const Tag &tag) const;
+
+ gcc_pure
+ bool Match(const Song &song) const;
+ };
+
+ std::list<Item> items;
+
+public:
+ SongFilter() = default;
+
+ gcc_nonnull(3)
+ SongFilter(unsigned tag, const char *value, bool fold_case=false);
+
+ ~SongFilter();
+
+ gcc_nonnull(2,3)
+ bool Parse(const char *tag, const char *value, bool fold_case=false);
+
+ gcc_nonnull(3)
+ bool Parse(unsigned argc, char *argv[], bool fold_case=false);
+
+ gcc_pure
+ bool Match(const Tag &tag) const;
+
+ gcc_pure
+ bool Match(const Song &song) const;
+};
+
+/**
+ * @return #TAG_NUM_OF_ITEM_TYPES on error
+ */
+gcc_pure
+unsigned
+locate_parse_type(const char *str);
+
+#endif
diff --git a/src/SongPointer.hxx b/src/SongPointer.hxx
new file mode 100644
index 000000000..ded3b3e1d
--- /dev/null
+++ b/src/SongPointer.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_SONG_POINTER_HXX
+#define MPD_SONG_POINTER_HXX
+
+#include "Song.hxx"
+
+#include <utility>
+
+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;
+
+ SongPointer &operator=(SongPointer &&other) {
+ std::swap(song, other.song);
+ return *this;
+ }
+
+ operator const Song *() const {
+ return song;
+ }
+
+ Song *Steal() {
+ auto result = song;
+ song = nullptr;
+ return result;
+ }
+};
+
+#endif
diff --git a/src/SongPrint.cxx b/src/SongPrint.cxx
new file mode 100644
index 000000000..65d27ca77
--- /dev/null
+++ b/src/SongPrint.cxx
@@ -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.
+ */
+
+#include "config.h"
+#include "SongPrint.hxx"
+#include "Song.hxx"
+#include "Directory.hxx"
+#include "TimePrint.hxx"
+#include "TagPrint.hxx"
+#include "Mapper.hxx"
+#include "Client.hxx"
+#include "util/UriUtil.hxx"
+
+#include <glib.h>
+
+void
+song_print_uri(Client *client, Song *song)
+{
+ if (song->IsInDatabase() && !song->parent->IsRoot()) {
+ client_printf(client, "%s%s/%s\n", SONG_FILE,
+ song->parent->GetPath(), song->uri);
+ } else {
+ char *allocated;
+ const char *uri;
+
+ uri = allocated = uri_remove_auth(song->uri);
+ if (uri == NULL)
+ uri = song->uri;
+
+ client_printf(client, "%s%s\n", SONG_FILE,
+ map_to_relative_path(uri));
+
+ g_free(allocated);
+ }
+}
+
+void
+song_print_info(Client *client, Song *song)
+{
+ song_print_uri(client, song);
+
+ if (song->end_ms > 0)
+ client_printf(client, "Range: %u.%03u-%u.%03u\n",
+ song->start_ms / 1000,
+ song->start_ms % 1000,
+ song->end_ms / 1000,
+ song->end_ms % 1000);
+ else if (song->start_ms > 0)
+ client_printf(client, "Range: %u.%03u-\n",
+ song->start_ms / 1000,
+ song->start_ms % 1000);
+
+ if (song->mtime > 0)
+ time_print(client, "Last-Modified", song->mtime);
+
+ if (song->tag != nullptr)
+ tag_print(client, *song->tag);
+}
diff --git a/src/SongPrint.hxx b/src/SongPrint.hxx
new file mode 100644
index 000000000..a82b54cfe
--- /dev/null
+++ b/src/SongPrint.hxx
@@ -0,0 +1,32 @@
+/*
+ * 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_SONG_PRINT_HXX
+#define MPD_SONG_PRINT_HXX
+
+struct Song;
+class Client;
+
+void
+song_print_info(Client *client, Song *song);
+
+void
+song_print_uri(Client *client, Song *song);
+
+#endif
diff --git a/src/SongSave.cxx b/src/SongSave.cxx
new file mode 100644
index 000000000..fcad320df
--- /dev/null
+++ b/src/SongSave.cxx
@@ -0,0 +1,133 @@
+/*
+ * 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 "SongSave.hxx"
+#include "Song.hxx"
+#include "TagSave.hxx"
+#include "Directory.hxx"
+#include "TextFile.hxx"
+#include "Tag.hxx"
+#include "util/StringUtil.hxx"
+
+#include <glib.h>
+
+#include <stdlib.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "song"
+
+#define SONG_MTIME "mtime"
+#define SONG_END "song_end"
+
+static GQuark
+song_save_quark(void)
+{
+ return g_quark_from_static_string("song_save");
+}
+
+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);
+
+ if (song->tag != nullptr)
+ tag_save(fp, *song->tag);
+
+ fprintf(fp, SONG_MTIME ": %li\n", (long)song->mtime);
+ fprintf(fp, SONG_END "\n");
+}
+
+Song *
+song_load(TextFile &file, Directory *parent, const char *uri,
+ GError **error_r)
+{
+ Song *song = parent != NULL
+ ? Song::NewFile(uri, parent)
+ : Song::NewRemote(uri);
+ char *line, *colon;
+ enum tag_type type;
+ const char *value;
+
+ while ((line = file.ReadLine()) != NULL &&
+ strcmp(line, SONG_END) != 0) {
+ colon = strchr(line, ':');
+ if (colon == NULL || colon == line) {
+ if (song->tag != NULL)
+ song->tag->EndAdd();
+ song->Free();
+
+ g_set_error(error_r, song_save_quark(), 0,
+ "unknown line in db: %s", line);
+ return NULL;
+ }
+
+ *colon++ = 0;
+ value = strchug_fast_c(colon);
+
+ if ((type = tag_name_parse(line)) != TAG_NUM_OF_ITEM_TYPES) {
+ if (!song->tag) {
+ song->tag = new Tag();
+ song->tag->BeginAdd();
+ }
+
+ song->tag->AddItem(type, value);
+ } else if (strcmp(line, "Time") == 0) {
+ if (!song->tag) {
+ song->tag = new Tag();
+ song->tag->BeginAdd();
+ }
+
+ song->tag->time = atoi(value);
+ } else if (strcmp(line, "Playlist") == 0) {
+ if (!song->tag) {
+ song->tag = new Tag();
+ song->tag->BeginAdd();
+ }
+
+ song->tag->has_playlist = strcmp(value, "yes") == 0;
+ } else if (strcmp(line, SONG_MTIME) == 0) {
+ song->mtime = 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, NULL, 10);
+ } else {
+ if (song->tag != NULL)
+ song->tag->EndAdd();
+ song->Free();
+
+ g_set_error(error_r, song_save_quark(), 0,
+ "unknown line in db: %s", line);
+ return NULL;
+ }
+ }
+
+ if (song->tag != NULL)
+ song->tag->EndAdd();
+
+ return song;
+}
diff --git a/src/SongSave.hxx b/src/SongSave.hxx
new file mode 100644
index 000000000..9fd6ba86c
--- /dev/null
+++ b/src/SongSave.hxx
@@ -0,0 +1,48 @@
+/*
+ * 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_SONG_SAVE_HXX
+#define MPD_SONG_SAVE_HXX
+
+#include "gerror.h"
+
+#include <stdio.h>
+
+#define SONG_BEGIN "song_begin: "
+
+struct Song;
+struct Directory;
+class TextFile;
+
+void
+song_save(FILE *fp, const Song *song);
+
+/**
+ * Loads a song from the input file. Reading stops after the
+ * "song_end" line.
+ *
+ * @param error_r location to store the error occurring, or NULL to
+ * ignore errors
+ * @return true on success, false on error
+ */
+Song *
+song_load(TextFile &file, Directory *parent, const char *uri,
+ GError **error_r);
+
+#endif
diff --git a/src/SongSort.cxx b/src/SongSort.cxx
new file mode 100644
index 000000000..0c154c763
--- /dev/null
+++ b/src/SongSort.cxx
@@ -0,0 +1,124 @@
+/*
+ * 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 "SongSort.hxx"
+#include "Song.hxx"
+#include "util/list.h"
+#include "Tag.hxx"
+
+extern "C" {
+#include "util/list_sort.h"
+}
+
+#include <glib.h>
+
+#include <assert.h>
+#include <stdlib.h>
+
+static const char *
+tag_get_value_checked(const Tag *tag, enum tag_type type)
+{
+ return tag != NULL
+ ? tag->GetValue(type)
+ : NULL;
+}
+
+static int
+compare_utf8_string(const char *a, const char *b)
+{
+ if (a == NULL)
+ return b == NULL ? 0 : -1;
+
+ if (b == NULL)
+ return 1;
+
+ return g_utf8_collate(a, b);
+}
+
+/**
+ * Compare two string tag values, ignoring case. Either one may be
+ * NULL.
+ */
+static int
+compare_string_tag_item(const Tag *a, const Tag *b,
+ enum tag_type type)
+{
+ return compare_utf8_string(tag_get_value_checked(a, type),
+ tag_get_value_checked(b, type));
+}
+
+/**
+ * Compare two tag values which should contain an integer value
+ * (e.g. disc or track number). Either one may be NULL.
+ */
+static int
+compare_number_string(const char *a, const char *b)
+{
+ long ai = a == NULL ? 0 : strtol(a, NULL, 10);
+ long bi = b == NULL ? 0 : strtol(b, NULL, 10);
+
+ if (ai <= 0)
+ return bi <= 0 ? 0 : -1;
+
+ if (bi <= 0)
+ return 1;
+
+ return ai - bi;
+}
+
+static int
+compare_tag_item(const Tag *a, const Tag *b, enum tag_type type)
+{
+ return compare_number_string(tag_get_value_checked(a, type),
+ tag_get_value_checked(b, type));
+}
+
+/* Only used for sorting/searchin a songvec, not general purpose compares */
+static int
+song_cmp(G_GNUC_UNUSED void *priv, struct list_head *_a, struct list_head *_b)
+{
+ const Song *a = (const Song *)_a;
+ const Song *b = (const Song *)_b;
+ int ret;
+
+ /* first sort by album */
+ ret = compare_string_tag_item(a->tag, b->tag, TAG_ALBUM);
+ if (ret != 0)
+ return ret;
+
+ /* then sort by disc */
+ ret = compare_tag_item(a->tag, b->tag, TAG_DISC);
+ if (ret != 0)
+ return ret;
+
+ /* then by track number */
+ ret = compare_tag_item(a->tag, b->tag, TAG_TRACK);
+ if (ret != 0)
+ return ret;
+
+ /* still no difference? compare file name */
+ return g_utf8_collate(a->uri, b->uri);
+}
+
+void
+song_list_sort(struct list_head *songs)
+{
+ list_sort(NULL, songs, song_cmp);
+}
diff --git a/src/SongSort.hxx b/src/SongSort.hxx
new file mode 100644
index 000000000..b3b67b0c0
--- /dev/null
+++ b/src/SongSort.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_SONG_SORT_HXX
+#define MPD_SONG_SORT_HXX
+
+struct list_head;
+
+void
+song_list_sort(struct list_head *songs);
+
+#endif
diff --git a/src/SongSticker.cxx b/src/SongSticker.cxx
new file mode 100644
index 000000000..589bc2a4a
--- /dev/null
+++ b/src/SongSticker.cxx
@@ -0,0 +1,150 @@
+/*
+ * 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 "SongSticker.hxx"
+#include "StickerDatabase.hxx"
+#include "Song.hxx"
+#include "Directory.hxx"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+
+char *
+sticker_song_get_value(const Song *song, const char *name)
+{
+ assert(song != NULL);
+ assert(song->IsInDatabase());
+
+ char *uri = song->GetURI();
+ char *value = sticker_load_value("song", uri, name);
+ g_free(uri);
+
+ return value;
+}
+
+bool
+sticker_song_set_value(const Song *song,
+ const char *name, const char *value)
+{
+ assert(song != NULL);
+ assert(song->IsInDatabase());
+
+ char *uri = song->GetURI();
+ bool ret = sticker_store_value("song", uri, name, value);
+ g_free(uri);
+
+ return ret;
+}
+
+bool
+sticker_song_delete(const Song *song)
+{
+ assert(song != NULL);
+ assert(song->IsInDatabase());
+
+ char *uri = song->GetURI();
+ bool ret = sticker_delete("song", uri);
+ g_free(uri);
+
+ return ret;
+}
+
+bool
+sticker_song_delete_value(const Song *song, const char *name)
+{
+ assert(song != NULL);
+ assert(song->IsInDatabase());
+
+ char *uri = song->GetURI();
+ bool success = sticker_delete_value("song", uri, name);
+ g_free(uri);
+
+ return success;
+}
+
+struct sticker *
+sticker_song_get(const Song *song)
+{
+ assert(song != NULL);
+ assert(song->IsInDatabase());
+
+ char *uri = song->GetURI();
+ struct sticker *sticker = sticker_load("song", uri);
+ g_free(uri);
+
+ return sticker;
+}
+
+struct sticker_song_find_data {
+ Directory *directory;
+ const char *base_uri;
+ size_t base_uri_length;
+
+ void (*func)(Song *song, const char *value,
+ void *user_data);
+ void *user_data;
+};
+
+static void
+sticker_song_find_cb(const char *uri, const char *value, void *user_data)
+{
+ struct sticker_song_find_data *data =
+ (struct sticker_song_find_data *)user_data;
+
+ if (memcmp(uri, data->base_uri, data->base_uri_length) != 0)
+ /* should not happen, ignore silently */
+ return;
+
+ Song *song = data->directory->LookupSong(uri + data->base_uri_length);
+ if (song != NULL)
+ data->func(song, value, data->user_data);
+}
+
+bool
+sticker_song_find(Directory *directory, const char *name,
+ void (*func)(Song *song, const char *value,
+ void *user_data),
+ void *user_data)
+{
+ struct sticker_song_find_data data;
+ data.directory = directory;
+ data.func = func;
+ data.user_data = user_data;
+
+ char *allocated;
+ data.base_uri = directory->GetPath();
+ if (*data.base_uri != 0)
+ /* append slash to base_uri */
+ data.base_uri = allocated =
+ g_strconcat(data.base_uri, "/", NULL);
+ else
+ /* searching in root directory - no trailing slash */
+ allocated = NULL;
+
+ data.base_uri_length = strlen(data.base_uri);
+
+ bool success = sticker_find("song", data.base_uri, name,
+ sticker_song_find_cb, &data);
+ g_free(allocated);
+
+ return success;
+}
diff --git a/src/SongSticker.hxx b/src/SongSticker.hxx
new file mode 100644
index 000000000..385aa59dc
--- /dev/null
+++ b/src/SongSticker.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_SONG_STICKER_HXX
+#define MPD_SONG_STICKER_HXX
+
+#include "gerror.h"
+
+struct Song;
+struct Directory;
+struct sticker;
+
+/**
+ * Returns one value from a song's sticker record. The caller must
+ * free the return value with g_free().
+ */
+char *
+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,
+ const char *name, const char *value);
+
+/**
+ * Deletes a sticker from the database. All values are deleted.
+ */
+bool
+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);
+
+/**
+ * Loads the sticker for the specified song.
+ *
+ * @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);
+
+/**
+ * Finds stickers with the specified name below the specified
+ * directory.
+ *
+ * Caller must lock the #db_mutex.
+ *
+ * @param directory the base directory to search in
+ * @param name the name of the sticker
+ * @return true on success (even if no sticker was found), false on
+ * failure
+ */
+bool
+sticker_song_find(Directory *directory, const char *name,
+ void (*func)(Song *song, const char *value,
+ void *user_data),
+ void *user_data);
+
+#endif
diff --git a/src/SongUpdate.cxx b/src/SongUpdate.cxx
new file mode 100644
index 000000000..f08e0c20a
--- /dev/null
+++ b/src/SongUpdate.cxx
@@ -0,0 +1,188 @@
+/*
+ * 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" /* must be first for large file support */
+#include "Song.hxx"
+#include "util/UriUtil.hxx"
+#include "Directory.hxx"
+#include "Mapper.hxx"
+#include "fs/Path.hxx"
+#include "fs/FileSystem.hxx"
+#include "Tag.hxx"
+#include "input_stream.h"
+#include "DecoderPlugin.hxx"
+#include "DecoderList.hxx"
+#include "TagHandler.hxx"
+#include "TagId3.hxx"
+#include "ApeTag.hxx"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdio.h>
+
+Song *
+Song::LoadFile(const char *path_utf8, Directory *parent)
+{
+ Song *song;
+ bool ret;
+
+ assert((parent == NULL) == g_path_is_absolute(path_utf8));
+ assert(!uri_has_scheme(path_utf8));
+ assert(strchr(path_utf8, '\n') == NULL);
+
+ song = NewFile(path_utf8, parent);
+
+ //in archive ?
+ if (parent != NULL && parent->device == DEVICE_INARCHIVE) {
+ ret = song->UpdateFileInArchive();
+ } else {
+ ret = song->UpdateFile();
+ }
+ if (!ret) {
+ song->Free();
+ return NULL;
+ }
+
+ return song;
+}
+
+/**
+ * Attempts to load APE or ID3 tags from the specified file.
+ */
+static bool
+tag_scan_fallback(const char *path,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ return tag_ape_scan2(path, handler, handler_ctx) ||
+ tag_id3_scan(path, handler, handler_ctx);
+}
+
+bool
+Song::UpdateFile()
+{
+ const char *suffix;
+ const struct decoder_plugin *plugin;
+ struct stat st;
+ struct input_stream *is = NULL;
+
+ assert(IsFile());
+
+ /* check if there's a suffix and a plugin */
+
+ suffix = uri_get_suffix(uri);
+ if (suffix == NULL)
+ return false;
+
+ plugin = decoder_plugin_from_suffix(suffix, NULL);
+ if (plugin == NULL)
+ return false;
+
+ const Path path_fs = map_song_fs(this);
+ if (path_fs.IsNull())
+ return false;
+
+ delete tag;
+ tag = nullptr;
+
+ if (!StatFile(path_fs, st) || !S_ISREG(st.st_mode)) {
+ return false;
+ }
+
+ mtime = st.st_mtime;
+
+ Mutex mutex;
+ Cond cond;
+
+ do {
+ /* load file tag */
+ tag = new Tag();
+ if (decoder_plugin_scan_file(plugin, path_fs.c_str(),
+ &full_tag_handler, tag))
+ break;
+
+ delete tag;
+ tag = nullptr;
+
+ /* fall back to stream tag */
+ if (plugin->scan_stream != NULL) {
+ /* open the input_stream (if not already
+ open) */
+ if (is == NULL) {
+ is = input_stream_open(path_fs.c_str(),
+ mutex, cond,
+ NULL);
+ }
+
+ /* now try the stream_tag() method */
+ if (is != NULL) {
+ tag = new Tag();
+ if (decoder_plugin_scan_stream(plugin, is,
+ &full_tag_handler,
+ tag))
+ break;
+
+ delete tag;
+ tag = nullptr;
+
+ input_stream_lock_seek(is, 0, SEEK_SET, NULL);
+ }
+ }
+
+ plugin = decoder_plugin_from_suffix(suffix, plugin);
+ } while (plugin != NULL);
+
+ if (is != NULL)
+ input_stream_close(is);
+
+ if (tag != nullptr && tag->IsEmpty())
+ tag_scan_fallback(path_fs.c_str(), &full_tag_handler, tag);
+
+ return tag != nullptr;
+}
+
+bool
+Song::UpdateFileInArchive()
+{
+ const char *suffix;
+ const struct decoder_plugin *plugin;
+
+ assert(IsFile());
+
+ /* check if there's a suffix and a plugin */
+
+ suffix = uri_get_suffix(uri);
+ if (suffix == NULL)
+ return false;
+
+ plugin = decoder_plugin_from_suffix(suffix, NULL);
+ if (plugin == NULL)
+ return false;
+
+ delete tag;
+
+ //accept every file that has music suffix
+ //because we don't support tag reading through
+ //input streams
+ tag = new Tag();
+
+ return true;
+}
diff --git a/src/StateFile.cxx b/src/StateFile.cxx
new file mode 100644
index 000000000..73af1777c
--- /dev/null
+++ b/src/StateFile.cxx
@@ -0,0 +1,123 @@
+/*
+ * 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 "StateFile.hxx"
+#include "OutputState.hxx"
+#include "PlaylistState.hxx"
+#include "TextFile.hxx"
+#include "Partition.hxx"
+#include "Volume.hxx"
+#include "event/Loop.hxx"
+
+#include <glib.h>
+#include <assert.h>
+#include <string.h>
+#include <errno.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "state_file"
+
+StateFile::StateFile(Path &&_path, const char *_path_utf8,
+ Partition &_partition, EventLoop &_loop)
+ :TimeoutMonitor(_loop), path(std::move(_path)), path_utf8(_path_utf8),
+ partition(_partition),
+ prev_volume_version(0), prev_output_version(0),
+ prev_playlist_version(0)
+{
+}
+
+void
+StateFile::RememberVersions()
+{
+ prev_volume_version = sw_volume_state_get_hash();
+ prev_output_version = audio_output_state_get_version();
+ prev_playlist_version = playlist_state_get_hash(&partition.playlist,
+ &partition.pc);
+}
+
+bool
+StateFile::IsModified() const
+{
+ return prev_volume_version != sw_volume_state_get_hash() ||
+ prev_output_version != audio_output_state_get_version() ||
+ prev_playlist_version != playlist_state_get_hash(&partition.playlist,
+ &partition.pc);
+}
+
+void
+StateFile::Write()
+{
+ g_debug("Saving state file %s", path_utf8.c_str());
+
+ FILE *fp = FOpen(path, FOpenMode::WriteText);
+ if (G_UNLIKELY(!fp)) {
+ g_warning("failed to create %s: %s",
+ path_utf8.c_str(), g_strerror(errno));
+ return;
+ }
+
+ save_sw_volume_state(fp);
+ audio_output_state_save(fp);
+ playlist_state_save(fp, &partition.playlist, &partition.pc);
+
+ fclose(fp);
+
+ RememberVersions();
+}
+
+void
+StateFile::Read()
+{
+ bool success;
+
+ g_debug("Loading state file %s", path_utf8.c_str());
+
+ TextFile file(path);
+ if (file.HasFailed()) {
+ g_warning("failed to open %s: %s",
+ path_utf8.c_str(), g_strerror(errno));
+ return;
+ }
+
+ const char *line;
+ while ((line = file.ReadLine()) != NULL) {
+ success = read_sw_volume_state(line) ||
+ audio_output_state_read(line) ||
+ playlist_state_restore(line, file, &partition.playlist,
+ &partition.pc);
+ if (!success)
+ g_warning("Unrecognized line in state file: %s", line);
+ }
+
+ RememberVersions();
+}
+
+void
+StateFile::CheckModified()
+{
+ if (!IsActive() && IsModified())
+ ScheduleSeconds(2 * 60);
+}
+
+void
+StateFile::OnTimeout()
+{
+ Write();
+}
diff --git a/src/StateFile.hxx b/src/StateFile.hxx
new file mode 100644
index 000000000..a97e4e2c3
--- /dev/null
+++ b/src/StateFile.hxx
@@ -0,0 +1,73 @@
+/*
+ * 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_STATE_FILE_HXX
+#define MPD_STATE_FILE_HXX
+
+#include "event/TimeoutMonitor.hxx"
+#include "fs/Path.hxx"
+#include "gcc.h"
+
+#include <string>
+
+struct Partition;
+
+class StateFile final : private TimeoutMonitor {
+ Path path;
+ std::string path_utf8;
+
+ Partition &partition;
+
+ /**
+ * These version numbers determine whether we need to save the state
+ * file. If nothing has changed, we won't let the hard drive spin up.
+ */
+ unsigned prev_volume_version, prev_output_version,
+ prev_playlist_version;
+
+public:
+ StateFile(Path &&path, const char *path_utf8,
+ Partition &partition, EventLoop &loop);
+
+ void Read();
+ void Write();
+
+ /**
+ * Schedules a write if MPD's state was modified.
+ */
+ void CheckModified();
+
+private:
+ /**
+ * Save the current state versions for use with IsModified().
+ */
+ void RememberVersions();
+
+ /**
+ * Check if MPD's state was modified since the last
+ * RememberVersions() call.
+ */
+ gcc_pure
+ bool IsModified() const;
+
+ /* virtual methods from TimeoutMonitor */
+ virtual void OnTimeout() override;
+};
+
+#endif /* STATE_FILE_H */
diff --git a/src/Stats.cxx b/src/Stats.cxx
new file mode 100644
index 000000000..354d26c59
--- /dev/null
+++ b/src/Stats.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"
+
+extern "C" {
+#include "stats.h"
+}
+
+#include "PlayerControl.hxx"
+#include "ClientInternal.hxx"
+#include "DatabaseSelection.hxx"
+#include "DatabaseGlue.hxx"
+#include "DatabasePlugin.hxx"
+#include "DatabaseSimple.hxx"
+
+struct stats stats;
+
+void stats_global_init(void)
+{
+ stats.timer = g_timer_new();
+}
+
+void stats_global_finish(void)
+{
+ g_timer_destroy(stats.timer);
+}
+
+void stats_update(void)
+{
+ GError *error = nullptr;
+
+ DatabaseStats stats2;
+
+ const DatabaseSelection selection("", true);
+ if (GetDatabase()->GetStats(selection, stats2, &error)) {
+ stats.song_count = stats2.song_count;
+ stats.song_duration = stats2.total_duration;
+ stats.artist_count = stats2.artist_count;
+ stats.album_count = stats2.album_count;
+ } else {
+ g_warning("%s", error->message);
+ g_error_free(error);
+
+ stats.song_count = 0;
+ stats.song_duration = 0;
+ stats.artist_count = 0;
+ stats.album_count = 0;
+ }
+}
+
+void
+stats_print(Client *client)
+{
+ client_printf(client,
+ "artists: %u\n"
+ "albums: %u\n"
+ "songs: %i\n"
+ "uptime: %li\n"
+ "playtime: %li\n"
+ "db_playtime: %li\n",
+ stats.artist_count,
+ stats.album_count,
+ stats.song_count,
+ (long)g_timer_elapsed(stats.timer, NULL),
+ (long)(client->player_control->GetTotalPlayTime() + 0.5),
+ stats.song_duration);
+
+ if (db_is_simple())
+ client_printf(client,
+ "db_update: %li\n",
+ (long)db_get_mtime());
+}
diff --git a/src/StickerCommands.cxx b/src/StickerCommands.cxx
new file mode 100644
index 000000000..5cbb7e984
--- /dev/null
+++ b/src/StickerCommands.cxx
@@ -0,0 +1,176 @@
+/*
+ * 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 "StickerCommands.hxx"
+#include "SongPrint.hxx"
+#include "DatabaseLock.hxx"
+#include "DatabasePlugin.hxx"
+#include "DatabaseGlue.hxx"
+#include "DatabaseSimple.hxx"
+#include "SongSticker.hxx"
+#include "StickerPrint.hxx"
+#include "StickerDatabase.hxx"
+#include "CommandError.hxx"
+#include "protocol/Result.hxx"
+
+#include <string.h>
+
+struct sticker_song_find_data {
+ Client *client;
+ const char *name;
+};
+
+static void
+sticker_song_find_print_cb(Song *song, const char *value,
+ gpointer user_data)
+{
+ struct sticker_song_find_data *data =
+ (struct sticker_song_find_data *)user_data;
+
+ song_print_uri(data->client, song);
+ sticker_print_value(data->client, data->name, value);
+}
+
+static enum command_return
+handle_sticker_song(Client *client, int argc, char *argv[])
+{
+ GError *error = nullptr;
+ const Database *db = GetDatabase(&error);
+ if (db == nullptr)
+ return print_error(client, error);
+
+ /* get song song_id key */
+ if (argc == 5 && strcmp(argv[1], "get") == 0) {
+ Song *song = db->GetSong(argv[3], &error);
+ if (song == nullptr)
+ return print_error(client, error);
+
+ char *value = sticker_song_get_value(song, argv[4]);
+ db->ReturnSong(song);
+ if (value == NULL) {
+ command_error(client, ACK_ERROR_NO_EXIST,
+ "no such sticker");
+ return COMMAND_RETURN_ERROR;
+ }
+
+ sticker_print_value(client, argv[4], value);
+ g_free(value);
+
+ return COMMAND_RETURN_OK;
+ /* list song song_id */
+ } else if (argc == 4 && strcmp(argv[1], "list") == 0) {
+ Song *song = db->GetSong(argv[3], &error);
+ if (song == nullptr)
+ return print_error(client, error);
+
+ sticker *sticker = sticker_song_get(song);
+ db->ReturnSong(song);
+ if (sticker) {
+ sticker_print(client, sticker);
+ sticker_free(sticker);
+ }
+
+ return COMMAND_RETURN_OK;
+ /* set song song_id id key */
+ } else if (argc == 6 && strcmp(argv[1], "set") == 0) {
+ Song *song = db->GetSong(argv[3], &error);
+ if (song == nullptr)
+ return print_error(client, error);
+
+ bool ret = sticker_song_set_value(song, argv[4], argv[5]);
+ db->ReturnSong(song);
+ if (!ret) {
+ command_error(client, ACK_ERROR_SYSTEM,
+ "failed to set sticker value");
+ return COMMAND_RETURN_ERROR;
+ }
+
+ return COMMAND_RETURN_OK;
+ /* delete song song_id [key] */
+ } else if ((argc == 4 || argc == 5) &&
+ strcmp(argv[1], "delete") == 0) {
+ Song *song = db->GetSong(argv[3], &error);
+ if (song == nullptr)
+ return print_error(client, error);
+
+ bool ret = argc == 4
+ ? sticker_song_delete(song)
+ : sticker_song_delete_value(song, argv[4]);
+ db->ReturnSong(song);
+ if (!ret) {
+ command_error(client, ACK_ERROR_SYSTEM,
+ "no such sticker");
+ return COMMAND_RETURN_ERROR;
+ }
+
+ return COMMAND_RETURN_OK;
+ /* find song dir key */
+ } else if (argc == 5 && strcmp(argv[1], "find") == 0) {
+ /* "sticker find song a/directory name" */
+ bool success;
+ struct sticker_song_find_data data = {
+ client,
+ argv[4],
+ };
+
+ db_lock();
+ Directory *directory = db_get_directory(argv[3]);
+ if (directory == NULL) {
+ db_unlock();
+ command_error(client, ACK_ERROR_NO_EXIST,
+ "no such directory");
+ return COMMAND_RETURN_ERROR;
+ }
+
+ success = sticker_song_find(directory, data.name,
+ sticker_song_find_print_cb, &data);
+ db_unlock();
+ if (!success) {
+ command_error(client, ACK_ERROR_SYSTEM,
+ "failed to set search sticker database");
+ return COMMAND_RETURN_ERROR;
+ }
+
+ return COMMAND_RETURN_OK;
+ } else {
+ command_error(client, ACK_ERROR_ARG, "bad request");
+ return COMMAND_RETURN_ERROR;
+ }
+}
+
+enum command_return
+handle_sticker(Client *client, int argc, char *argv[])
+{
+ assert(argc >= 4);
+
+ if (!sticker_enabled()) {
+ command_error(client, ACK_ERROR_UNKNOWN,
+ "sticker database is disabled");
+ return COMMAND_RETURN_ERROR;
+ }
+
+ if (strcmp(argv[2], "song") == 0)
+ return handle_sticker_song(client, argc, argv);
+ else {
+ command_error(client, ACK_ERROR_ARG,
+ "unknown sticker domain");
+ return COMMAND_RETURN_ERROR;
+ }
+}
diff --git a/src/StickerCommands.hxx b/src/StickerCommands.hxx
new file mode 100644
index 000000000..840bd33d5
--- /dev/null
+++ b/src/StickerCommands.hxx
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+#ifndef MPD_STICKER_COMMANDS_HXX
+#define MPD_STICKER_COMMANDS_HXX
+
+#include "command.h"
+
+class Client;
+
+enum command_return
+handle_sticker(Client *client, int argc, char *argv[]);
+
+#endif
diff --git a/src/StickerDatabase.cxx b/src/StickerDatabase.cxx
new file mode 100644
index 000000000..2d77e4b63
--- /dev/null
+++ b/src/StickerDatabase.cxx
@@ -0,0 +1,631 @@
+/*
+ * 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 "StickerDatabase.hxx"
+#include "Idle.hxx"
+
+#include <string>
+#include <map>
+
+#include <glib.h>
+#include <sqlite3.h>
+#include <assert.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "sticker"
+
+#if SQLITE_VERSION_NUMBER < 3003009
+#define sqlite3_prepare_v2 sqlite3_prepare
+#endif
+
+struct sticker {
+ std::map<std::string, std::string> table;
+};
+
+enum sticker_sql {
+ STICKER_SQL_GET,
+ STICKER_SQL_LIST,
+ STICKER_SQL_UPDATE,
+ STICKER_SQL_INSERT,
+ STICKER_SQL_DELETE,
+ STICKER_SQL_DELETE_VALUE,
+ STICKER_SQL_FIND,
+};
+
+static const char *const sticker_sql[] = {
+ //[STICKER_SQL_GET] =
+ "SELECT value FROM sticker WHERE type=? AND uri=? AND name=?",
+ //[STICKER_SQL_LIST] =
+ "SELECT name,value FROM sticker WHERE type=? AND uri=?",
+ //[STICKER_SQL_UPDATE] =
+ "UPDATE sticker SET value=? WHERE type=? AND uri=? AND name=?",
+ //[STICKER_SQL_INSERT] =
+ "INSERT INTO sticker(type,uri,name,value) VALUES(?, ?, ?, ?)",
+ //[STICKER_SQL_DELETE] =
+ "DELETE FROM sticker WHERE type=? AND uri=?",
+ //[STICKER_SQL_DELETE_VALUE] =
+ "DELETE FROM sticker WHERE type=? AND uri=? AND name=?",
+ //[STICKER_SQL_FIND] =
+ "SELECT uri,value FROM sticker WHERE type=? AND uri LIKE (? || '%') AND name=?",
+};
+
+static const char sticker_sql_create[] =
+ "CREATE TABLE IF NOT EXISTS sticker("
+ " type VARCHAR NOT NULL, "
+ " uri VARCHAR NOT NULL, "
+ " name VARCHAR NOT NULL, "
+ " value VARCHAR NOT NULL"
+ ");"
+ "CREATE UNIQUE INDEX IF NOT EXISTS"
+ " sticker_value ON sticker(type, uri, name);"
+ "";
+
+static sqlite3 *sticker_db;
+static sqlite3_stmt *sticker_stmt[G_N_ELEMENTS(sticker_sql)];
+
+static GQuark
+sticker_quark(void)
+{
+ return g_quark_from_static_string("sticker");
+}
+
+static sqlite3_stmt *
+sticker_prepare(const char *sql, GError **error_r)
+{
+ int ret;
+ sqlite3_stmt *stmt;
+
+ ret = sqlite3_prepare_v2(sticker_db, sql, -1, &stmt, NULL);
+ if (ret != SQLITE_OK) {
+ g_set_error(error_r, sticker_quark(), ret,
+ "sqlite3_prepare_v2() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return NULL;
+ }
+
+ return stmt;
+}
+
+bool
+sticker_global_init(const char *path, GError **error_r)
+{
+ int ret;
+
+ if (path == NULL)
+ /* not configured */
+ return true;
+
+ /* open/create the sqlite database */
+
+ ret = sqlite3_open(path, &sticker_db);
+ if (ret != SQLITE_OK) {
+ g_set_error(error_r, sticker_quark(), ret,
+ "Failed to open sqlite database '%s': %s",
+ path, sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ /* create the table and index */
+
+ ret = sqlite3_exec(sticker_db, sticker_sql_create, NULL, NULL, NULL);
+ if (ret != SQLITE_OK) {
+ g_set_error(error_r, sticker_quark(), ret,
+ "Failed to create sticker table: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ /* prepare the statements we're going to use */
+
+ for (unsigned i = 0; i < G_N_ELEMENTS(sticker_sql); ++i) {
+ assert(sticker_sql[i] != NULL);
+
+ sticker_stmt[i] = sticker_prepare(sticker_sql[i], error_r);
+ if (sticker_stmt[i] == NULL)
+ return false;
+ }
+
+ return true;
+}
+
+void
+sticker_global_finish(void)
+{
+ if (sticker_db == NULL)
+ /* not configured */
+ return;
+
+ for (unsigned i = 0; i < G_N_ELEMENTS(sticker_stmt); ++i) {
+ assert(sticker_stmt[i] != NULL);
+
+ sqlite3_finalize(sticker_stmt[i]);
+ }
+
+ sqlite3_close(sticker_db);
+}
+
+bool
+sticker_enabled(void)
+{
+ return sticker_db != NULL;
+}
+
+char *
+sticker_load_value(const char *type, const char *uri, const char *name)
+{
+ sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_GET];
+ int ret;
+ char *value;
+
+ assert(sticker_enabled());
+ assert(type != NULL);
+ assert(uri != NULL);
+ assert(name != NULL);
+
+ if (*name == 0)
+ return NULL;
+
+ sqlite3_reset(stmt);
+
+ ret = sqlite3_bind_text(stmt, 1, type, -1, NULL);
+ if (ret != SQLITE_OK) {
+ g_warning("sqlite3_bind_text() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return NULL;
+ }
+
+ ret = sqlite3_bind_text(stmt, 2, uri, -1, NULL);
+ if (ret != SQLITE_OK) {
+ g_warning("sqlite3_bind_text() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return NULL;
+ }
+
+ ret = sqlite3_bind_text(stmt, 3, name, -1, NULL);
+ if (ret != SQLITE_OK) {
+ g_warning("sqlite3_bind_text() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return NULL;
+ }
+
+ do {
+ ret = sqlite3_step(stmt);
+ } while (ret == SQLITE_BUSY);
+
+ if (ret == SQLITE_ROW) {
+ /* record found */
+ value = g_strdup((const char*)sqlite3_column_text(stmt, 0));
+ } else if (ret == SQLITE_DONE) {
+ /* no record found */
+ value = NULL;
+ } else {
+ /* error */
+ g_warning("sqlite3_step() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return NULL;
+ }
+
+ sqlite3_reset(stmt);
+ sqlite3_clear_bindings(stmt);
+
+ return value;
+}
+
+static bool
+sticker_list_values(std::map<std::string, std::string> &table,
+ const char *type, const char *uri)
+{
+ sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_LIST];
+ int ret;
+
+ assert(type != NULL);
+ assert(uri != NULL);
+ assert(sticker_enabled());
+
+ sqlite3_reset(stmt);
+
+ ret = sqlite3_bind_text(stmt, 1, type, -1, NULL);
+ if (ret != SQLITE_OK) {
+ g_warning("sqlite3_bind_text() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ ret = sqlite3_bind_text(stmt, 2, uri, -1, NULL);
+ if (ret != SQLITE_OK) {
+ g_warning("sqlite3_bind_text() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ do {
+ ret = sqlite3_step(stmt);
+ switch (ret) {
+ const char *name, *value;
+
+ case SQLITE_ROW:
+ name = (const char*)sqlite3_column_text(stmt, 0);
+ value = (const char*)sqlite3_column_text(stmt, 1);
+
+ table.insert(std::make_pair(name, value));
+ break;
+ case SQLITE_DONE:
+ break;
+ case SQLITE_BUSY:
+ /* no op */
+ break;
+ default:
+ g_warning("sqlite3_step() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
+ } while (ret != SQLITE_DONE);
+
+ sqlite3_reset(stmt);
+ sqlite3_clear_bindings(stmt);
+
+ return true;
+}
+
+static bool
+sticker_update_value(const char *type, const char *uri,
+ const char *name, const char *value)
+{
+ sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_UPDATE];
+ int ret;
+
+ assert(type != NULL);
+ assert(uri != NULL);
+ assert(name != NULL);
+ assert(*name != 0);
+ assert(value != NULL);
+
+ assert(sticker_enabled());
+
+ sqlite3_reset(stmt);
+
+ ret = sqlite3_bind_text(stmt, 1, value, -1, NULL);
+ if (ret != SQLITE_OK) {
+ g_warning("sqlite3_bind_text() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ ret = sqlite3_bind_text(stmt, 2, type, -1, NULL);
+ if (ret != SQLITE_OK) {
+ g_warning("sqlite3_bind_text() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ ret = sqlite3_bind_text(stmt, 3, uri, -1, NULL);
+ if (ret != SQLITE_OK) {
+ g_warning("sqlite3_bind_text() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ ret = sqlite3_bind_text(stmt, 4, name, -1, NULL);
+ if (ret != SQLITE_OK) {
+ g_warning("sqlite3_bind_text() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ do {
+ ret = sqlite3_step(stmt);
+ } while (ret == SQLITE_BUSY);
+
+ if (ret != SQLITE_DONE) {
+ g_warning("sqlite3_step() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ ret = sqlite3_changes(sticker_db);
+
+ sqlite3_reset(stmt);
+ sqlite3_clear_bindings(stmt);
+
+ idle_add(IDLE_STICKER);
+ return ret > 0;
+}
+
+static bool
+sticker_insert_value(const char *type, const char *uri,
+ const char *name, const char *value)
+{
+ sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_INSERT];
+ int ret;
+
+ assert(type != NULL);
+ assert(uri != NULL);
+ assert(name != NULL);
+ assert(*name != 0);
+ assert(value != NULL);
+
+ assert(sticker_enabled());
+
+ sqlite3_reset(stmt);
+
+ ret = sqlite3_bind_text(stmt, 1, type, -1, NULL);
+ if (ret != SQLITE_OK) {
+ g_warning("sqlite3_bind_text() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ ret = sqlite3_bind_text(stmt, 2, uri, -1, NULL);
+ if (ret != SQLITE_OK) {
+ g_warning("sqlite3_bind_text() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ ret = sqlite3_bind_text(stmt, 3, name, -1, NULL);
+ if (ret != SQLITE_OK) {
+ g_warning("sqlite3_bind_text() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ ret = sqlite3_bind_text(stmt, 4, value, -1, NULL);
+ if (ret != SQLITE_OK) {
+ g_warning("sqlite3_bind_text() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ do {
+ ret = sqlite3_step(stmt);
+ } while (ret == SQLITE_BUSY);
+
+ if (ret != SQLITE_DONE) {
+ g_warning("sqlite3_step() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ sqlite3_reset(stmt);
+ sqlite3_clear_bindings(stmt);
+
+
+ idle_add(IDLE_STICKER);
+ return true;
+}
+
+bool
+sticker_store_value(const char *type, const char *uri,
+ const char *name, const char *value)
+{
+ assert(sticker_enabled());
+ assert(type != NULL);
+ assert(uri != NULL);
+ assert(name != NULL);
+ assert(value != NULL);
+
+ if (*name == 0)
+ return false;
+
+ return sticker_update_value(type, uri, name, value) ||
+ sticker_insert_value(type, uri, name, value);
+}
+
+bool
+sticker_delete(const char *type, const char *uri)
+{
+ sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_DELETE];
+ int ret;
+
+ assert(sticker_enabled());
+ assert(type != NULL);
+ assert(uri != NULL);
+
+ sqlite3_reset(stmt);
+
+ ret = sqlite3_bind_text(stmt, 1, type, -1, NULL);
+ if (ret != SQLITE_OK) {
+ g_warning("sqlite3_bind_text() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ ret = sqlite3_bind_text(stmt, 2, uri, -1, NULL);
+ if (ret != SQLITE_OK) {
+ g_warning("sqlite3_bind_text() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ do {
+ ret = sqlite3_step(stmt);
+ } while (ret == SQLITE_BUSY);
+
+ if (ret != SQLITE_DONE) {
+ g_warning("sqlite3_step() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ sqlite3_reset(stmt);
+ sqlite3_clear_bindings(stmt);
+
+ idle_add(IDLE_STICKER);
+ return true;
+}
+
+bool
+sticker_delete_value(const char *type, const char *uri, const char *name)
+{
+ sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_DELETE_VALUE];
+ int ret;
+
+ assert(sticker_enabled());
+ assert(type != NULL);
+ assert(uri != NULL);
+
+ sqlite3_reset(stmt);
+
+ ret = sqlite3_bind_text(stmt, 1, type, -1, NULL);
+ if (ret != SQLITE_OK) {
+ g_warning("sqlite3_bind_text() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ ret = sqlite3_bind_text(stmt, 2, uri, -1, NULL);
+ if (ret != SQLITE_OK) {
+ g_warning("sqlite3_bind_text() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ ret = sqlite3_bind_text(stmt, 3, name, -1, NULL);
+ if (ret != SQLITE_OK) {
+ g_warning("sqlite3_bind_text() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ do {
+ ret = sqlite3_step(stmt);
+ } while (ret == SQLITE_BUSY);
+
+ if (ret != SQLITE_DONE) {
+ g_warning("sqlite3_step() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ ret = sqlite3_changes(sticker_db);
+
+ sqlite3_reset(stmt);
+ sqlite3_clear_bindings(stmt);
+
+ idle_add(IDLE_STICKER);
+ return ret > 0;
+}
+
+void
+sticker_free(struct sticker *sticker)
+{
+ delete sticker;
+}
+
+const char *
+sticker_get_value(const struct sticker *sticker, const char *name)
+{
+ auto i = sticker->table.find(name);
+ if (i == sticker->table.end())
+ return nullptr;
+
+ return i->second.c_str();
+}
+
+void
+sticker_foreach(const struct sticker *sticker,
+ void (*func)(const char *name, const char *value,
+ gpointer user_data),
+ gpointer user_data)
+{
+ for (const auto &i : sticker->table)
+ func(i.first.c_str(), i.second.c_str(), user_data);
+}
+
+struct sticker *
+sticker_load(const char *type, const char *uri)
+{
+ sticker s;
+
+ if (!sticker_list_values(s.table, type, uri))
+ return NULL;
+
+ if (s.table.empty())
+ /* don't return empty sticker objects */
+ return NULL;
+
+ return new sticker(std::move(s));
+}
+
+bool
+sticker_find(const char *type, const char *base_uri, const char *name,
+ void (*func)(const char *uri, const char *value,
+ gpointer user_data),
+ gpointer user_data)
+{
+ sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_FIND];
+ int ret;
+
+ assert(type != NULL);
+ assert(name != NULL);
+ assert(func != NULL);
+ assert(sticker_enabled());
+
+ sqlite3_reset(stmt);
+
+ ret = sqlite3_bind_text(stmt, 1, type, -1, NULL);
+ if (ret != SQLITE_OK) {
+ g_warning("sqlite3_bind_text() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ if (base_uri == NULL)
+ base_uri = "";
+
+ ret = sqlite3_bind_text(stmt, 2, base_uri, -1, NULL);
+ if (ret != SQLITE_OK) {
+ g_warning("sqlite3_bind_text() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ ret = sqlite3_bind_text(stmt, 3, name, -1, NULL);
+ if (ret != SQLITE_OK) {
+ g_warning("sqlite3_bind_text() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ do {
+ ret = sqlite3_step(stmt);
+ switch (ret) {
+ case SQLITE_ROW:
+ func((const char*)sqlite3_column_text(stmt, 0),
+ (const char*)sqlite3_column_text(stmt, 1),
+ user_data);
+ break;
+ case SQLITE_DONE:
+ break;
+ case SQLITE_BUSY:
+ /* no op */
+ break;
+ default:
+ g_warning("sqlite3_step() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
+ } while (ret != SQLITE_DONE);
+
+ sqlite3_reset(stmt);
+ sqlite3_clear_bindings(stmt);
+
+ return true;
+}
diff --git a/src/StickerDatabase.hxx b/src/StickerDatabase.hxx
new file mode 100644
index 000000000..90ff9b066
--- /dev/null
+++ b/src/StickerDatabase.hxx
@@ -0,0 +1,157 @@
+/*
+ * 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.
+ */
+
+/*
+ * This is the sticker database library. It is the backend of all the
+ * sticker code in MPD.
+ *
+ * "Stickers" are pieces of information attached to existing MPD
+ * objects (e.g. song files, directories, albums). Clients can create
+ * arbitrary name/value pairs. MPD itself does not assume any special
+ * meaning in them.
+ *
+ * The goal is to allow clients to share additional (possibly dynamic)
+ * information about songs, which is neither stored on the client (not
+ * available to other clients), nor stored in the song files (MPD has
+ * no write access).
+ *
+ * Client developers should create a standard for common sticker
+ * names, to ensure interoperability.
+ *
+ * Examples: song ratings; statistics; deferred tag writes; lyrics;
+ * ...
+ *
+ */
+
+#ifndef MPD_STICKER_DATABASE_HXX
+#define MPD_STICKER_DATABASE_HXX
+
+#include "gerror.h"
+
+struct sticker;
+
+/**
+ * Opens the sticker database (if path is not NULL).
+ *
+ * @param error_r location to store the error occurring, or NULL to
+ * ignore errors
+ * @return true on success, false on error
+ */
+bool
+sticker_global_init(const char *path, GError **error_r);
+
+/**
+ * Close the sticker database.
+ */
+void
+sticker_global_finish(void);
+
+/**
+ * Returns true if the sticker database is configured and available.
+ */
+bool
+sticker_enabled(void);
+
+/**
+ * Returns one value from an object's sticker record. The caller must
+ * free the return value with g_free().
+ */
+char *
+sticker_load_value(const char *type, const char *uri, const char *name);
+
+/**
+ * Sets a sticker value in the specified object. Overwrites existing
+ * values.
+ */
+bool
+sticker_store_value(const char *type, const char *uri,
+ const char *name, const char *value);
+
+/**
+ * Deletes a sticker from the database. All sticker values of the
+ * specified object are deleted.
+ */
+bool
+sticker_delete(const char *type, const char *uri);
+
+/**
+ * Deletes a sticker value. Fails if no sticker with this name
+ * exists.
+ */
+bool
+sticker_delete_value(const char *type, const char *uri, const char *name);
+
+/**
+ * Frees resources held by the sticker object.
+ *
+ * @param sticker the sticker object to be freed
+ */
+void
+sticker_free(struct sticker *sticker);
+
+/**
+ * Determines a single value in a sticker.
+ *
+ * @param sticker the sticker object
+ * @param name the name of the sticker
+ * @return the sticker value, or NULL if none was found
+ */
+const char *
+sticker_get_value(const struct sticker *sticker, const char *name);
+
+/**
+ * Iterates over all sticker items in a sticker.
+ *
+ * @param sticker the sticker object
+ * @param func a callback function
+ * @param user_data an opaque pointer for the callback function
+ */
+void
+sticker_foreach(const struct sticker *sticker,
+ void (*func)(const char *name, const char *value,
+ void *user_data),
+ void *user_data);
+
+/**
+ * Loads the sticker for the specified resource.
+ *
+ * @param type the resource type, e.g. "song"
+ * @param uri the URI of the resource, e.g. the song path
+ * @return a sticker object, or NULL on error or if there is no sticker
+ */
+struct sticker *
+sticker_load(const char *type, const char *uri);
+
+/**
+ * Finds stickers with the specified name below the specified URI.
+ *
+ * @param type the resource type, e.g. "song"
+ * @param base_uri the URI prefix of the resources, or NULL if all
+ * resources should be searched
+ * @param name the name of the sticker
+ * @return true on success (even if no sticker was found), false on
+ * failure
+ */
+bool
+sticker_find(const char *type, const char *base_uri, const char *name,
+ void (*func)(const char *uri, const char *value,
+ void *user_data),
+ void *user_data);
+
+#endif
diff --git a/src/StickerPrint.cxx b/src/StickerPrint.cxx
new file mode 100644
index 000000000..1708ee4f0
--- /dev/null
+++ b/src/StickerPrint.cxx
@@ -0,0 +1,44 @@
+/*
+ * 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 "StickerPrint.hxx"
+#include "StickerDatabase.hxx"
+#include "Client.hxx"
+
+void
+sticker_print_value(Client *client,
+ const char *name, const char *value)
+{
+ client_printf(client, "sticker: %s=%s\n", name, value);
+}
+
+static void
+print_sticker_cb(const char *name, const char *value, void *data)
+{
+ Client *client = (Client *)data;
+
+ sticker_print_value(client, name, value);
+}
+
+void
+sticker_print(Client *client, const struct sticker *sticker)
+{
+ sticker_foreach(sticker, print_sticker_cb, client);
+}
diff --git a/src/StickerPrint.hxx b/src/StickerPrint.hxx
new file mode 100644
index 000000000..27225a494
--- /dev/null
+++ b/src/StickerPrint.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_STICKER_PRINT_HXX
+#define MPD_STICKER_PRINT_HXX
+
+struct sticker;
+class Client;
+
+/**
+ * Sends one sticker value to the client.
+ */
+void
+sticker_print_value(Client *client, const char *name, const char *value);
+
+/**
+ * Sends all sticker values to the client.
+ */
+void
+sticker_print(Client *client, const struct sticker *sticker);
+
+#endif
diff --git a/src/Tag.cxx b/src/Tag.cxx
new file mode 100644
index 000000000..39560d49d
--- /dev/null
+++ b/src/Tag.cxx
@@ -0,0 +1,508 @@
+/*
+ * 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 "Tag.hxx"
+#include "TagInternal.hxx"
+#include "TagPool.hxx"
+#include "conf.h"
+#include "Song.hxx"
+#include "mpd_error.h"
+
+#include <glib.h>
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/**
+ * Maximum number of items managed in the bulk list; if it is
+ * exceeded, we switch back to "normal" reallocation.
+ */
+#define BULK_MAX 64
+
+static struct {
+#ifndef NDEBUG
+ bool busy;
+#endif
+ TagItem *items[BULK_MAX];
+} bulk;
+
+bool ignore_tag_items[TAG_NUM_OF_ITEM_TYPES];
+
+enum tag_type
+tag_name_parse(const char *name)
+{
+ assert(name != nullptr);
+
+ for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) {
+ assert(tag_item_names[i] != nullptr);
+
+ if (strcmp(name, tag_item_names[i]) == 0)
+ return (enum tag_type)i;
+ }
+
+ return TAG_NUM_OF_ITEM_TYPES;
+}
+
+enum tag_type
+tag_name_parse_i(const char *name)
+{
+ assert(name != nullptr);
+
+ for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) {
+ assert(tag_item_names[i] != nullptr);
+
+ if (g_ascii_strcasecmp(name, tag_item_names[i]) == 0)
+ return (enum tag_type)i;
+ }
+
+ return TAG_NUM_OF_ITEM_TYPES;
+}
+
+static size_t
+items_size(const Tag &tag)
+{
+ return tag.num_items * sizeof(TagItem *);
+}
+
+void tag_lib_init(void)
+{
+ const char *value;
+ int quit = 0;
+ char *temp;
+ char *s;
+ char *c;
+ enum tag_type type;
+
+ /* parse the "metadata_to_use" config parameter below */
+
+ /* ignore comments by default */
+ ignore_tag_items[TAG_COMMENT] = true;
+
+ value = config_get_string(CONF_METADATA_TO_USE, nullptr);
+ if (value == nullptr)
+ return;
+
+ memset(ignore_tag_items, true, TAG_NUM_OF_ITEM_TYPES);
+
+ if (0 == g_ascii_strcasecmp(value, "none"))
+ return;
+
+ temp = c = s = g_strdup(value);
+ while (!quit) {
+ if (*s == ',' || *s == '\0') {
+ if (*s == '\0')
+ quit = 1;
+ *s = '\0';
+
+ c = g_strstrip(c);
+ if (*c == 0)
+ continue;
+
+ type = tag_name_parse_i(c);
+ if (type == TAG_NUM_OF_ITEM_TYPES)
+ MPD_ERROR("error parsing metadata item \"%s\"",
+ c);
+
+ ignore_tag_items[type] = false;
+
+ s++;
+ c = s;
+ }
+ s++;
+ }
+
+ g_free(temp);
+}
+
+void
+Tag::Clear()
+{
+ time = -1;
+ has_playlist = false;
+
+ tag_pool_lock.lock();
+ for (unsigned i = 0; i < num_items; ++i)
+ tag_pool_put_item(items[i]);
+ tag_pool_lock.unlock();
+
+ if (items == bulk.items) {
+#ifndef NDEBUG
+ assert(bulk.busy);
+ bulk.busy = false;
+#endif
+ } else
+ g_free(items);
+
+ items = nullptr;
+ num_items = 0;
+}
+
+void
+Tag::DeleteItem(unsigned idx)
+{
+ assert(idx < num_items);
+ --num_items;
+
+ tag_pool_lock.lock();
+ tag_pool_put_item(items[idx]);
+ tag_pool_lock.unlock();
+
+ if (num_items - idx > 0) {
+ memmove(items + idx, items + idx + 1,
+ (num_items - idx) * sizeof(items[0]));
+ }
+
+ if (num_items > 0) {
+ items = (TagItem **)
+ g_realloc(items, items_size(*this));
+ } else {
+ g_free(items);
+ items = nullptr;
+ }
+}
+
+void
+Tag::ClearItemsByType(tag_type type)
+{
+ for (unsigned i = 0; i < num_items; i++) {
+ if (items[i]->type == type) {
+ DeleteItem(i);
+ /* decrement since when just deleted this node */
+ i--;
+ }
+ }
+}
+
+Tag::~Tag()
+{
+ tag_pool_lock.lock();
+ for (int i = num_items; --i >= 0; )
+ tag_pool_put_item(items[i]);
+ tag_pool_lock.unlock();
+
+ if (items == bulk.items) {
+#ifndef NDEBUG
+ assert(bulk.busy);
+ bulk.busy = false;
+#endif
+ } else
+ 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));
+
+ tag_pool_lock.lock();
+ for (unsigned i = 0; i < num_items; i++)
+ items[i] = tag_pool_dup_item(other.items[i]);
+ tag_pool_lock.unlock();
+ }
+}
+
+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;
+}
+
+Tag *
+Tag::MergeReplace(Tag *base, Tag *add)
+{
+ if (add == nullptr)
+ return base;
+
+ if (base == nullptr)
+ return add;
+
+ Tag *tag = Merge(*base, *add);
+ delete base;
+ delete add;
+
+ return tag;
+}
+
+const char *
+Tag::GetValue(tag_type type) const
+{
+ assert(type < TAG_NUM_OF_ITEM_TYPES);
+
+ for (unsigned i = 0; i < num_items; i++)
+ if (items[i]->type == type)
+ return items[i]->value;
+
+ return nullptr;
+}
+
+bool
+Tag::HasType(tag_type type) const
+{
+ return GetValue(type) != nullptr;
+}
+
+bool
+Tag::Equals(const Tag &other) const
+{
+ if (time != other.time)
+ return false;
+
+ if (num_items != other.num_items)
+ return false;
+
+ for (unsigned i = 0; i < num_items; i++) {
+ if (items[i]->type != other.items[i]->type)
+ return false;
+ if (strcmp(items[i]->value, other.items[i]->value)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Replace invalid sequences with the question mark.
+ */
+static char *
+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);
+
+ do {
+ dest[end - src] = '?';
+ } while (!g_utf8_validate(end + 1, (src + length) - (end + 1), &end));
+
+ return dest;
+}
+
+static char *
+fix_utf8(const char *str, size_t length)
+{
+ const gchar *end;
+ char *temp;
+ gsize written;
+
+ assert(str != nullptr);
+
+ /* check if the string is already valid UTF-8 */
+ if (g_utf8_validate(str, length, &end))
+ return nullptr;
+
+ /* 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)
+ /* success! */
+ return temp;
+
+ /* no, still broken - there's no medication, just patch
+ invalid sequences */
+ return patch_utf8(str, length, end);
+}
+
+void
+Tag::BeginAdd()
+{
+ assert(!bulk.busy);
+ assert(items == nullptr);
+ assert(num_items == 0);
+
+#ifndef NDEBUG
+ bulk.busy = true;
+#endif
+ items = bulk.items;
+}
+
+void
+Tag::EndAdd()
+{
+ if (items == bulk.items) {
+ assert(num_items <= BULK_MAX);
+
+ if (num_items > 0) {
+ /* copy the tag items from the bulk list over
+ to a new list (which fits exactly) */
+ items = (TagItem **)
+ g_malloc(items_size(*this));
+ memcpy(items, bulk.items, items_size(*this));
+ } else
+ items = nullptr;
+ }
+
+#ifndef NDEBUG
+ bulk.busy = false;
+#endif
+}
+
+static bool
+char_is_non_printable(unsigned char ch)
+{
+ return ch < 0x20;
+}
+
+static const char *
+find_non_printable(const char *p, size_t length)
+{
+ for (size_t i = 0; i < length; ++i)
+ if (char_is_non_printable(p[i]))
+ return p + i;
+
+ return nullptr;
+}
+
+/**
+ * Clears all non-printable characters, convert them to space.
+ * Returns nullptr if nothing needs to be cleared.
+ */
+static char *
+clear_non_printable(const char *p, size_t length)
+{
+ const char *first = find_non_printable(p, length);
+ char *dest;
+
+ if (first == nullptr)
+ return nullptr;
+
+ dest = g_strndup(p, length);
+
+ for (size_t i = first - p; i < length; ++i)
+ if (char_is_non_printable(dest[i]))
+ dest[i] = ' ';
+
+ return dest;
+}
+
+static char *
+fix_tag_value(const char *p, size_t length)
+{
+ char *utf8, *cleared;
+
+ utf8 = fix_utf8(p, length);
+ if (utf8 != nullptr) {
+ p = utf8;
+ length = strlen(p);
+ }
+
+ cleared = clear_non_printable(p, length);
+ if (cleared == nullptr)
+ cleared = utf8;
+ else
+ g_free(utf8);
+
+ return cleared;
+}
+
+void
+Tag::AddItemInternal(tag_type type, const char *value, size_t len)
+{
+ unsigned int i = num_items;
+ char *p;
+
+ p = fix_tag_value(value, len);
+ if (p != nullptr) {
+ value = p;
+ len = strlen(value);
+ }
+
+ num_items++;
+
+ if (items != bulk.items)
+ /* bulk mode disabled */
+ items = (TagItem **)
+ g_realloc(items, items_size(*this));
+ else if (num_items >= BULK_MAX) {
+ /* bulk list already full - switch back to non-bulk */
+ assert(bulk.busy);
+
+ items = (TagItem **)g_malloc(items_size(*this));
+ memcpy(items, bulk.items,
+ items_size(*this) - sizeof(TagItem *));
+ }
+
+ tag_pool_lock.lock();
+ items[i] = tag_pool_get_item(type, value, len);
+ tag_pool_lock.unlock();
+
+ g_free(p);
+}
+
+void
+Tag::AddItem(tag_type 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(tag_type type, const char *value)
+{
+ AddItem(type, value, strlen(value));
+}
diff --git a/src/Tag.hxx b/src/Tag.hxx
new file mode 100644
index 000000000..2c5f599e2
--- /dev/null
+++ b/src/Tag.hxx
@@ -0,0 +1,223 @@
+/*
+ * 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_HXX
+#define MPD_TAG_HXX
+
+#include "TagType.h"
+#include "gcc.h"
+
+#include <algorithm>
+
+#include <stddef.h>
+
+/**
+ * One tag value. It is a mapping of #tag_type to am arbitrary string
+ * value. Each tag can have multiple items of one tag type (although
+ * few clients support that).
+ */
+struct TagItem {
+ /** the type of this item */
+ enum tag_type type;
+
+ /**
+ * the value of this tag; this is a variable length string
+ */
+ char value[sizeof(long)];
+} gcc_packed;
+
+/**
+ * The meta information about a song file. It is a MPD specific
+ * subset of tags (e.g. from ID3, vorbis comments, ...).
+ */
+struct Tag {
+ /**
+ * The duration of the song (in seconds). A value of zero
+ * means that the length is unknown. If the duration is
+ * really between zero and one second, you should round up to
+ * 1.
+ */
+ int time;
+
+ /**
+ * Does this file have an embedded playlist (e.g. embedded CUE
+ * sheet)?
+ */
+ bool has_playlist;
+
+ /** an array of tag items */
+ TagItem **items;
+
+ /** the total number of tag items in the #items array */
+ unsigned num_items;
+
+ /**
+ * Create an empty tag.
+ */
+ Tag():time(-1), has_playlist(false),
+ items(nullptr), num_items(0) {}
+
+ Tag(const Tag &other);
+
+ Tag(Tag &&other)
+ :time(other.time), has_playlist(other.has_playlist),
+ items(other.items), num_items(other.num_items) {
+ other.items = nullptr;
+ other.num_items = 0;
+ }
+
+ /**
+ * Free the tag object and all its items.
+ */
+ ~Tag();
+
+ Tag &operator=(const Tag &other) = delete;
+
+ Tag &operator=(Tag &&other) {
+ time = other.time;
+ has_playlist = other.has_playlist;
+ std::swap(items, other.items);
+ std::swap(num_items, other.num_items);
+ return *this;
+ }
+
+ /**
+ * Returns true if the tag contains no items. This ignores the "time"
+ * attribute.
+ */
+ bool IsEmpty() const {
+ return num_items == 0;
+ }
+
+ /**
+ * Returns true if the tag contains any information.
+ */
+ bool IsDefined() const {
+ return !IsEmpty() || time >= 0;
+ }
+
+ /**
+ * Clear everything, as if this was a new Tag object.
+ */
+ void Clear();
+
+ void DeleteItem(unsigned i);
+
+ /**
+ * Clear all tag items with the specified type.
+ */
+ void ClearItemsByType(tag_type type);
+
+ /**
+ * Gives an optional hint to the tag library that we will now
+ * add several tag items; this is used by the library to
+ * optimize memory allocation. Only one tag may be in this
+ * state, and this tag must not have any items yet. You must
+ * call tag_end_add() when you are done.
+ */
+ void BeginAdd();
+
+ /**
+ * Finishes the operation started with tag_begin_add().
+ */
+ void EndAdd();
+
+ /**
+ * 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(tag_type 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(tag_type type, const char *value);
+
+ /**
+ * Merges the data from two tags. If both tags share data for the
+ * same tag_type, only data from "add" is used.
+ *
+ * @return a newly allocated tag
+ */
+ gcc_malloc
+ static Tag *Merge(const Tag &base, const Tag &add);
+
+ /**
+ * Merges the data from two tags. Any of the two may be NULL. Both
+ * are freed by this function.
+ *
+ * @return a newly allocated tag
+ */
+ gcc_malloc
+ static Tag *MergeReplace(Tag *base, Tag *add);
+
+ /**
+ * Returns the first value of the specified tag type, or NULL if none
+ * is present in this tag object.
+ */
+ gcc_pure
+ const char *GetValue(tag_type type) const;
+
+ /**
+ * Checks whether the tag contains one or more items with
+ * the specified type.
+ */
+ bool HasType(tag_type type) const;
+
+ /**
+ * Compares two tags, including the duration and all tag items. The
+ * order of the tag items matters.
+ */
+ gcc_pure
+ bool Equals(const Tag &other) const;
+
+private:
+ void AddItemInternal(tag_type type, const char *value, size_t len);
+};
+
+/**
+ * Parse the string, and convert it into a #tag_type. Returns
+ * #TAG_NUM_OF_ITEM_TYPES if the string could not be recognized.
+ */
+enum tag_type
+tag_name_parse(const char *name);
+
+/**
+ * Parse the string, and convert it into a #tag_type. Returns
+ * #TAG_NUM_OF_ITEM_TYPES if the string could not be recognized.
+ *
+ * Case does not matter.
+ */
+enum tag_type
+tag_name_parse_i(const char *name);
+
+/**
+ * Initializes the tag library.
+ */
+void
+tag_lib_init();
+
+#endif
diff --git a/src/TagFile.cxx b/src/TagFile.cxx
new file mode 100644
index 000000000..9201dd85b
--- /dev/null
+++ b/src/TagFile.cxx
@@ -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.
+ */
+
+#include "config.h"
+#include "TagFile.hxx"
+#include "util/UriUtil.hxx"
+#include "DecoderList.hxx"
+#include "DecoderPlugin.hxx"
+#include "input_stream.h"
+
+#include <assert.h>
+#include <unistd.h> /* for SEEK_SET */
+
+bool
+tag_file_scan(const char *path_fs,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ assert(path_fs != NULL);
+ assert(handler != NULL);
+
+ /* check if there's a suffix and a plugin */
+
+ const char *suffix = uri_get_suffix(path_fs);
+ if (suffix == NULL)
+ return false;
+
+ const struct decoder_plugin *plugin =
+ decoder_plugin_from_suffix(suffix, NULL);
+ if (plugin == NULL)
+ return false;
+
+ struct input_stream *is = NULL;
+ Mutex mutex;
+ Cond cond;
+
+ do {
+ /* load file tag */
+ if (decoder_plugin_scan_file(plugin, path_fs,
+ handler, handler_ctx))
+ break;
+
+ /* fall back to stream tag */
+ if (plugin->scan_stream != NULL) {
+ /* open the input_stream (if not already
+ open) */
+ if (is == nullptr)
+ is = input_stream_open(path_fs, mutex, cond,
+ NULL);
+
+ /* now try the stream_tag() method */
+ if (is != NULL) {
+ if (decoder_plugin_scan_stream(plugin, is,
+ handler,
+ handler_ctx))
+ break;
+
+ input_stream_lock_seek(is, 0, SEEK_SET, NULL);
+ }
+ }
+
+ plugin = decoder_plugin_from_suffix(suffix, plugin);
+ } while (plugin != NULL);
+
+ if (is != NULL)
+ input_stream_close(is);
+
+ return plugin != NULL;
+}
diff --git a/src/TagFile.hxx b/src/TagFile.hxx
new file mode 100644
index 000000000..910f7b82b
--- /dev/null
+++ b/src/TagFile.hxx
@@ -0,0 +1,35 @@
+/*
+ * 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_FILE_HXX
+#define MPD_TAG_FILE_HXX
+
+#include "check.h"
+
+struct tag_handler;
+
+/**
+ * Scan the tags of a song file. Invokes matching decoder plugins,
+ * but does not invoke the special "APE" and "ID3" scanners.
+ */
+bool
+tag_file_scan(const char *path_fs,
+ const struct tag_handler *handler, void *handler_ctx);
+
+#endif
diff --git a/src/TagHandler.cxx b/src/TagHandler.cxx
new file mode 100644
index 000000000..227c48c9c
--- /dev/null
+++ b/src/TagHandler.cxx
@@ -0,0 +1,62 @@
+/*
+ * 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 "TagHandler.hxx"
+#include "Tag.hxx"
+
+#include <glib.h>
+
+static void
+add_tag_duration(unsigned seconds, void *ctx)
+{
+ Tag *tag = (Tag *)ctx;
+
+ tag->time = seconds;
+}
+
+static void
+add_tag_tag(enum tag_type type, const char *value, void *ctx)
+{
+ Tag *tag = (Tag *)ctx;
+
+ tag->AddItem(type, value);
+}
+
+const struct tag_handler add_tag_handler = {
+ add_tag_duration,
+ add_tag_tag,
+ nullptr,
+};
+
+static void
+full_tag_pair(const char *name, G_GNUC_UNUSED const char *value, void *ctx)
+{
+ Tag *tag = (Tag *)ctx;
+
+ if (g_ascii_strcasecmp(name, "cuesheet") == 0)
+ tag->has_playlist = true;
+}
+
+const struct tag_handler full_tag_handler = {
+ add_tag_duration,
+ add_tag_tag,
+ full_tag_pair,
+};
+
diff --git a/src/TagHandler.hxx b/src/TagHandler.hxx
new file mode 100644
index 000000000..3303dd27e
--- /dev/null
+++ b/src/TagHandler.hxx
@@ -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.
+ */
+
+#ifndef MPD_TAG_HANDLER_HXX
+#define MPD_TAG_HANDLER_HXX
+
+#include "check.h"
+#include "TagType.h"
+
+#include <assert.h>
+
+/**
+ * A callback table for receiving metadata of a song.
+ */
+struct tag_handler {
+ /**
+ * Declare the duration of a song, in seconds. Do not call
+ * this when the duration could not be determined, because
+ * there is no magic value for "unknown duration".
+ */
+ void (*duration)(unsigned seconds, void *ctx);
+
+ /**
+ * A tag has been read.
+ *
+ * @param the value of the tag; the pointer will become
+ * invalid after returning
+ */
+ void (*tag)(enum tag_type type, const char *value, void *ctx);
+
+ /**
+ * A name-value pair has been read. It is the codec specific
+ * representation of tags.
+ */
+ void (*pair)(const char *key, const char *value, void *ctx);
+};
+
+static inline void
+tag_handler_invoke_duration(const struct tag_handler *handler, void *ctx,
+ unsigned seconds)
+{
+ assert(handler != nullptr);
+
+ if (handler->duration != nullptr)
+ handler->duration(seconds, ctx);
+}
+
+static inline void
+tag_handler_invoke_tag(const struct tag_handler *handler, void *ctx,
+ enum tag_type type, const char *value)
+{
+ assert(handler != nullptr);
+ assert((unsigned)type < TAG_NUM_OF_ITEM_TYPES);
+ assert(value != nullptr);
+
+ if (handler->tag != nullptr)
+ handler->tag(type, value, ctx);
+}
+
+static inline void
+tag_handler_invoke_pair(const struct tag_handler *handler, void *ctx,
+ const char *name, const char *value)
+{
+ assert(handler != nullptr);
+ assert(name != nullptr);
+ assert(value != nullptr);
+
+ if (handler->pair != nullptr)
+ handler->pair(name, value, ctx);
+}
+
+/**
+ * This #tag_handler implementation adds tag values to a #tag object
+ * (casted from the context pointer).
+ */
+extern const struct tag_handler add_tag_handler;
+
+/**
+ * This #tag_handler implementation adds tag values to a #tag object
+ * (casted from the context pointer), and supports the has_playlist
+ * attribute.
+ */
+extern const struct tag_handler full_tag_handler;
+
+#endif
diff --git a/src/TagId3.cxx b/src/TagId3.cxx
new file mode 100644
index 000000000..6b2174fe5
--- /dev/null
+++ b/src/TagId3.cxx
@@ -0,0 +1,587 @@
+/*
+ * 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 "TagId3.hxx"
+#include "TagHandler.hxx"
+#include "TagTable.hxx"
+#include "Tag.hxx"
+
+extern "C" {
+#include "riff.h"
+#include "aiff.h"
+}
+
+#include "conf.h"
+#include "io_error.h"
+
+#include <glib.h>
+#include <id3tag.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "id3"
+
+# ifndef ID3_FRAME_COMPOSER
+# define ID3_FRAME_COMPOSER "TCOM"
+# endif
+# ifndef ID3_FRAME_DISC
+# define ID3_FRAME_DISC "TPOS"
+# endif
+
+#ifndef ID3_FRAME_ARTIST_SORT
+#define ID3_FRAME_ARTIST_SORT "TSOP"
+#endif
+
+#ifndef ID3_FRAME_ALBUM_ARTIST_SORT
+#define ID3_FRAME_ALBUM_ARTIST_SORT "TSO2" /* this one is unofficial, introduced by Itunes */
+#endif
+
+#ifndef ID3_FRAME_ALBUM_ARTIST
+#define ID3_FRAME_ALBUM_ARTIST "TPE2"
+#endif
+
+static inline bool
+tag_is_id3v1(struct id3_tag *tag)
+{
+ return (id3_tag_options(tag, 0, 0) & ID3_TAG_OPTION_ID3V1) != 0;
+}
+
+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);
+ if (field == nullptr)
+ return nullptr;
+
+ ucs4 = id3_field_getstring(field);
+ if (ucs4 == nullptr)
+ return nullptr;
+
+ return id3_ucs4_utf8duplicate(ucs4);
+}
+
+/* This will try to convert a string to utf-8,
+ */
+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;
+
+ /* use encoding field here? */
+ if (is_id3v1 &&
+ (encoding = config_get_string(CONF_ID3V1_ENCODING, nullptr)) != nullptr) {
+ isostr = id3_ucs4_latin1duplicate(ucs4);
+ if (G_UNLIKELY(!isostr)) {
+ return nullptr;
+ }
+
+ utf8 = (id3_utf8_t *)
+ g_convert_with_fallback((const char*)isostr, -1,
+ "utf-8", encoding,
+ nullptr, nullptr,
+ nullptr, nullptr);
+ if (utf8 == nullptr) {
+ g_debug("Unable to convert %s string to UTF-8: '%s'",
+ encoding, isostr);
+ g_free(isostr);
+ return nullptr;
+ }
+ g_free(isostr);
+ } else {
+ utf8 = id3_ucs4_utf8duplicate(ucs4);
+ if (G_UNLIKELY(!utf8)) {
+ return nullptr;
+ }
+ }
+
+ utf8_stripped = (id3_utf8_t *)g_strdup(g_strstrip((gchar *)utf8));
+ g_free(utf8);
+
+ return utf8_stripped;
+}
+
+/**
+ * Import a "Text information frame" (ID3v2.4.0 section 4.2). It
+ * contains 2 fields:
+ *
+ * - encoding
+ * - string list
+ */
+static void
+tag_id3_import_text_frame(struct id3_tag *tag, const struct id3_frame *frame,
+ enum tag_type 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);
+ if (field == nullptr || field->type != ID3_FIELD_TYPE_TEXTENCODING)
+ return;
+
+ /* process the value(s) */
+
+ field = id3_frame_field(frame, 1);
+ if (field == nullptr || field->type != ID3_FIELD_TYPE_STRINGLIST)
+ return;
+
+ /* Get the number of strings available */
+ nstrings = id3_field_getnstrings(field);
+ for (i = 0; i < nstrings; i++) {
+ 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);
+ if (utf8 == nullptr)
+ continue;
+
+ tag_handler_invoke_tag(handler, handler_ctx,
+ type, (const char *)utf8);
+ g_free(utf8);
+ }
+}
+
+/**
+ * Import all text frames with the specified id (ID3v2.4.0 section
+ * 4.2). This is a wrapper for tag_id3_import_text_frame().
+ */
+static void
+tag_id3_import_text(struct id3_tag *tag, const char *id, enum tag_type type,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ const struct id3_frame *frame;
+ for (unsigned i = 0;
+ (frame = id3_tag_findframe(tag, id, i)) != nullptr; ++i)
+ tag_id3_import_text_frame(tag, frame, type,
+ handler, handler_ctx);
+}
+
+/**
+ * Import a "Comment frame" (ID3v2.4.0 section 4.10). It
+ * contains 4 fields:
+ *
+ * - encoding
+ * - language
+ * - string
+ * - full string (we use this one)
+ */
+static void
+tag_id3_import_comment_frame(struct id3_tag *tag,
+ const struct id3_frame *frame, enum tag_type type,
+ 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);
+ if (field == nullptr)
+ return;
+
+ ucs4 = id3_field_getfullstring(field);
+ if (ucs4 == nullptr)
+ return;
+
+ utf8 = import_id3_string(tag_is_id3v1(tag), ucs4);
+ if (utf8 == nullptr)
+ return;
+
+ tag_handler_invoke_tag(handler, handler_ctx, type, (const char *)utf8);
+ g_free(utf8);
+}
+
+/**
+ * Import all comment frames (ID3v2.4.0 section 4.10). This is a
+ * wrapper for tag_id3_import_comment_frame().
+ */
+static void
+tag_id3_import_comment(struct id3_tag *tag, const char *id, enum tag_type type,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ const struct id3_frame *frame;
+ for (unsigned i = 0;
+ (frame = id3_tag_findframe(tag, id, i)) != nullptr; ++i)
+ tag_id3_import_comment_frame(tag, frame, type,
+ handler, handler_ctx);
+}
+
+/**
+ * Parse a TXXX name, and convert it to a tag_type enum value.
+ * Returns TAG_NUM_OF_ITEM_TYPES if the TXXX name is not understood.
+ */
+static enum tag_type
+tag_id3_parse_txxx_name(const char *name)
+{
+ static const struct tag_table txxx_tags[] = {
+ { "ALBUMARTISTSORT", TAG_ALBUM_ARTIST_SORT },
+ { "MusicBrainz Artist Id", TAG_MUSICBRAINZ_ARTISTID },
+ { "MusicBrainz Album Id", TAG_MUSICBRAINZ_ALBUMID },
+ { "MusicBrainz Album Artist Id",
+ TAG_MUSICBRAINZ_ALBUMARTISTID },
+ { "MusicBrainz Track Id", TAG_MUSICBRAINZ_TRACKID },
+ { nullptr, TAG_NUM_OF_ITEM_TYPES }
+ };
+
+ return tag_table_lookup(txxx_tags, name);
+}
+
+/**
+ * Import all known MusicBrainz tags from TXXX frames.
+ */
+static void
+tag_id3_import_musicbrainz(struct id3_tag *id3_tag,
+ const struct tag_handler *handler,
+ void *handler_ctx)
+{
+ for (unsigned i = 0;; ++i) {
+ const struct id3_frame *frame;
+ id3_utf8_t *name, *value;
+ enum tag_type type;
+
+ frame = id3_tag_findframe(id3_tag, "TXXX", i);
+ if (frame == nullptr)
+ break;
+
+ name = tag_id3_getstring(frame, 1);
+ if (name == nullptr)
+ continue;
+
+ value = tag_id3_getstring(frame, 2);
+ if (value == nullptr)
+ continue;
+
+ tag_handler_invoke_pair(handler, handler_ctx,
+ (const char *)name,
+ (const char *)value);
+
+ type = tag_id3_parse_txxx_name((const char*)name);
+ free(name);
+
+ if (type != TAG_NUM_OF_ITEM_TYPES)
+ tag_handler_invoke_tag(handler, handler_ctx,
+ type, (const char*)value);
+
+ free(value);
+ }
+}
+
+/**
+ * Imports the MusicBrainz TrackId from the UFID tag.
+ */
+static void
+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);
+ if (frame == nullptr)
+ break;
+
+ field = id3_frame_field(frame, 0);
+ if (field == nullptr)
+ continue;
+
+ name = id3_field_getlatin1(field);
+ if (name == nullptr ||
+ strcmp((const char *)name, "http://musicbrainz.org") != 0)
+ continue;
+
+ field = id3_frame_field(frame, 1);
+ if (field == nullptr)
+ continue;
+
+ value = id3_field_getbinarydata(field, &length);
+ if (value == nullptr || length == 0)
+ continue;
+
+ char *p = g_strndup((const char *)value, length);
+ tag_handler_invoke_tag(handler, handler_ctx,
+ TAG_MUSICBRAINZ_TRACKID, p);
+ g_free(p);
+ }
+}
+
+void
+scan_id3_tag(struct id3_tag *tag,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ tag_id3_import_text(tag, ID3_FRAME_ARTIST, TAG_ARTIST,
+ handler, handler_ctx);
+ tag_id3_import_text(tag, ID3_FRAME_ALBUM_ARTIST,
+ TAG_ALBUM_ARTIST, handler, handler_ctx);
+ tag_id3_import_text(tag, ID3_FRAME_ARTIST_SORT,
+ TAG_ARTIST_SORT, handler, handler_ctx);
+ tag_id3_import_text(tag, ID3_FRAME_ALBUM_ARTIST_SORT,
+ TAG_ALBUM_ARTIST_SORT, handler, handler_ctx);
+ tag_id3_import_text(tag, ID3_FRAME_TITLE, TAG_TITLE,
+ handler, handler_ctx);
+ tag_id3_import_text(tag, ID3_FRAME_ALBUM, TAG_ALBUM,
+ handler, handler_ctx);
+ tag_id3_import_text(tag, ID3_FRAME_TRACK, TAG_TRACK,
+ handler, handler_ctx);
+ tag_id3_import_text(tag, ID3_FRAME_YEAR, TAG_DATE,
+ handler, handler_ctx);
+ tag_id3_import_text(tag, ID3_FRAME_GENRE, TAG_GENRE,
+ handler, handler_ctx);
+ tag_id3_import_text(tag, ID3_FRAME_COMPOSER, TAG_COMPOSER,
+ handler, handler_ctx);
+ tag_id3_import_text(tag, "TPE3", TAG_PERFORMER,
+ handler, handler_ctx);
+ tag_id3_import_text(tag, "TPE4", TAG_PERFORMER, handler, handler_ctx);
+ tag_id3_import_comment(tag, ID3_FRAME_COMMENT, TAG_COMMENT,
+ handler, handler_ctx);
+ tag_id3_import_text(tag, ID3_FRAME_DISC, TAG_DISC,
+ handler, handler_ctx);
+
+ tag_id3_import_musicbrainz(tag, handler, handler_ctx);
+ tag_id3_import_ufid(tag, handler, handler_ctx);
+}
+
+Tag *
+tag_id3_import(struct id3_tag *tag)
+{
+ Tag *ret = new Tag();
+
+ scan_id3_tag(tag, &add_tag_handler, ret);
+
+ if (ret->IsEmpty()) {
+ delete ret;
+ ret = nullptr;
+ }
+
+ return ret;
+}
+
+static int
+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
+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;
+ 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);
+ if (query_buffer_size <= 0)
+ return nullptr;
+
+ /* Look for a tag header */
+ 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;
+
+ int tag_buffer_size = fill_buffer(tag_buffer, tag_size,
+ stream, offset, whence);
+ if (tag_buffer_size < tag_size) {
+ g_free(tag_buffer);
+ return nullptr;
+ }
+
+ tag = id3_tag_parse(tag_buffer, tag_buffer_size);
+
+ g_free(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);
+ if (!tag) {
+ return nullptr;
+ } else if (tag_is_id3v1(tag)) {
+ /* id3v1 tags don't belong here */
+ id3_tag_delete(tag);
+ return nullptr;
+ }
+
+ /* We have an id3v2 tag, so let's look for SEEK frames */
+ 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));
+ if (seek < 0)
+ break;
+
+ /* Get the tag specified by the SEEK frame */
+ seektag = tag_id3_read(stream, seek, SEEK_CUR);
+ if (!seektag || tag_is_id3v1(seektag))
+ break;
+
+ /* Replace the old tag with the new one */
+ id3_tag_delete(tag);
+ tag = seektag;
+ }
+
+ return tag;
+}
+
+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);
+
+ /* Get the id3v2 tag size from the footer (located before v1tag) */
+ 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);
+ if (!tag)
+ return v1tag;
+
+ /* We have an id3v2 tag, so ditch v1tag */
+ id3_tag_delete(v1tag);
+
+ return tag;
+}
+
+static struct id3_tag *
+tag_id3_riff_aiff_load(FILE *file)
+{
+ size_t size = riff_seek_id3(file);
+ if (size == 0)
+ size = aiff_seek_id3(file);
+ if (size == 0)
+ return nullptr;
+
+ if (size > 4 * 1024 * 1024)
+ /* too large, don't allocate so much memory */
+ return nullptr;
+
+ id3_byte_t *buffer = (id3_byte_t *)g_malloc(size);
+ size_t ret = fread(buffer, size, 1, file);
+ if (ret != 1) {
+ g_warning("Failed to read RIFF chunk");
+ g_free(buffer);
+ return nullptr;
+ }
+
+ struct id3_tag *tag = id3_tag_parse(buffer, size);
+ g_free(buffer);
+ return tag;
+}
+
+struct id3_tag *
+tag_id3_load(const char *path_fs, GError **error_r)
+{
+ FILE *file = fopen(path_fs, "rb");
+ if (file == nullptr) {
+ g_set_error(error_r, errno_quark(), errno,
+ "Failed to open file %s: %s",
+ path_fs, g_strerror(errno));
+ return nullptr;
+ }
+
+ struct id3_tag *tag = tag_id3_find_from_beginning(file);
+ if (tag == nullptr) {
+ tag = tag_id3_riff_aiff_load(file);
+ if (tag == nullptr)
+ tag = tag_id3_find_from_end(file);
+ }
+
+ fclose(file);
+ return tag;
+}
+
+bool
+tag_id3_scan(const char *path_fs,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ GError *error = nullptr;
+ struct id3_tag *tag = tag_id3_load(path_fs, &error);
+ if (tag == nullptr) {
+ if (error != nullptr) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ }
+
+ return false;
+ }
+
+ scan_id3_tag(tag, handler, handler_ctx);
+ id3_tag_delete(tag);
+ return true;
+}
diff --git a/src/TagId3.hxx b/src/TagId3.hxx
new file mode 100644
index 000000000..d359306e9
--- /dev/null
+++ b/src/TagId3.hxx
@@ -0,0 +1,70 @@
+/*
+ * 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_ID3_HXX
+#define MPD_TAG_ID3_HXX
+
+#include "check.h"
+#include "gcc.h"
+#include "gerror.h"
+
+struct tag_handler;
+struct Tag;
+struct id3_tag;
+
+#ifdef HAVE_ID3TAG
+
+bool
+tag_id3_scan(const char *path_fs,
+ const struct tag_handler *handler, void *handler_ctx);
+
+Tag *
+tag_id3_import(struct id3_tag *);
+
+/**
+ * Loads the ID3 tags from the file into a libid3tag object. The
+ * return value must be freed with id3_tag_delete().
+ *
+ * @return NULL on error or if no ID3 tag was found in the file (no
+ * GError will be set)
+ */
+struct id3_tag *
+tag_id3_load(const char *path_fs, GError **error_r);
+
+/**
+ * Import all tags from the provided id3_tag *tag
+ *
+ */
+void
+scan_id3_tag(struct id3_tag *tag,
+ const struct tag_handler *handler, void *handler_ctx);
+
+#else
+
+static inline bool
+tag_id3_scan(gcc_unused const char *path_fs,
+ gcc_unused const struct tag_handler *handler,
+ gcc_unused void *handler_ctx)
+{
+ return false;
+}
+
+#endif
+
+#endif
diff --git a/src/TagInternal.hxx b/src/TagInternal.hxx
new file mode 100644
index 000000000..8172d1319
--- /dev/null
+++ b/src/TagInternal.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_TAG_INTERNAL_HXX
+#define MPD_TAG_INTERNAL_HXX
+
+#include "TagType.h"
+
+extern bool ignore_tag_items[TAG_NUM_OF_ITEM_TYPES];
+
+#endif
diff --git a/src/TagNames.c b/src/TagNames.c
new file mode 100644
index 000000000..2e318f913
--- /dev/null
+++ b/src/TagNames.c
@@ -0,0 +1,44 @@
+/*
+ * 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 "TagType.h"
+
+const char *tag_item_names[TAG_NUM_OF_ITEM_TYPES] = {
+ [TAG_ARTIST] = "Artist",
+ [TAG_ARTIST_SORT] = "ArtistSort",
+ [TAG_ALBUM] = "Album",
+ [TAG_ALBUM_ARTIST] = "AlbumArtist",
+ [TAG_ALBUM_ARTIST_SORT] = "AlbumArtistSort",
+ [TAG_TITLE] = "Title",
+ [TAG_TRACK] = "Track",
+ [TAG_NAME] = "Name",
+ [TAG_GENRE] = "Genre",
+ [TAG_DATE] = "Date",
+ [TAG_COMPOSER] = "Composer",
+ [TAG_PERFORMER] = "Performer",
+ [TAG_COMMENT] = "Comment",
+ [TAG_DISC] = "Disc",
+
+ /* MusicBrainz tags from http://musicbrainz.org/doc/MusicBrainzTag */
+ [TAG_MUSICBRAINZ_ARTISTID] = "MUSICBRAINZ_ARTISTID",
+ [TAG_MUSICBRAINZ_ALBUMID] = "MUSICBRAINZ_ALBUMID",
+ [TAG_MUSICBRAINZ_ALBUMARTISTID] = "MUSICBRAINZ_ALBUMARTISTID",
+ [TAG_MUSICBRAINZ_TRACKID] = "MUSICBRAINZ_TRACKID",
+};
diff --git a/src/TagPool.cxx b/src/TagPool.cxx
new file mode 100644
index 000000000..5a0b33c47
--- /dev/null
+++ b/src/TagPool.cxx
@@ -0,0 +1,155 @@
+/*
+ * 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 "TagPool.hxx"
+#include "Tag.hxx"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+
+Mutex tag_pool_lock;
+
+#define NUM_SLOTS 4096
+
+struct slot {
+ struct slot *next;
+ unsigned char ref;
+ TagItem item;
+} mpd_packed;
+
+static struct slot *slots[NUM_SLOTS];
+
+static inline unsigned
+calc_hash_n(enum tag_type type, const char *p, size_t length)
+{
+ unsigned hash = 5381;
+
+ assert(p != nullptr);
+
+ while (length-- > 0)
+ hash = (hash << 5) + hash + *p++;
+
+ return hash ^ type;
+}
+
+static inline unsigned
+calc_hash(enum tag_type type, const char *p)
+{
+ unsigned hash = 5381;
+
+ assert(p != nullptr);
+
+ while (*p != 0)
+ hash = (hash << 5) + hash + *p++;
+
+ return hash ^ type;
+}
+
+static inline struct slot *
+tag_item_to_slot(TagItem *item)
+{
+ return (struct slot*)(((char*)item) - offsetof(struct slot, item));
+}
+
+static struct slot *slot_alloc(struct slot *next,
+ enum tag_type 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;
+}
+
+TagItem *
+tag_pool_get_item(enum tag_type type, const char *value, size_t length)
+{
+ struct slot **slot_p, *slot;
+
+ slot_p = &slots[calc_hash_n(type, value, length) % NUM_SLOTS];
+ for (slot = *slot_p; slot != nullptr; slot = slot->next) {
+ if (slot->item.type == type &&
+ length == strlen(slot->item.value) &&
+ memcmp(value, slot->item.value, length) == 0 &&
+ slot->ref < 0xff) {
+ assert(slot->ref > 0);
+ ++slot->ref;
+ return &slot->item;
+ }
+ }
+
+ slot = slot_alloc(*slot_p, type, value, length);
+ *slot_p = slot;
+ return &slot->item;
+}
+
+TagItem *
+tag_pool_dup_item(TagItem *item)
+{
+ struct slot *slot = tag_item_to_slot(item);
+
+ assert(slot->ref > 0);
+
+ if (slot->ref < 0xff) {
+ ++slot->ref;
+ return item;
+ } else {
+ /* the reference counter overflows above 0xff;
+ duplicate the item, and start with 1 */
+ size_t length = strlen(item->value);
+ struct slot **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_p = slot;
+ return &slot->item;
+ }
+}
+
+void
+tag_pool_put_item(TagItem *item)
+{
+ struct slot **slot_p, *slot;
+
+ slot = tag_item_to_slot(item);
+ assert(slot->ref > 0);
+ --slot->ref;
+
+ if (slot->ref > 0)
+ return;
+
+ for (slot_p = &slots[calc_hash(item->type, item->value) % NUM_SLOTS];
+ *slot_p != slot;
+ slot_p = &(*slot_p)->next) {
+ assert(*slot_p != nullptr);
+ }
+
+ *slot_p = slot->next;
+ g_free(slot);
+}
diff --git a/src/TagPool.hxx b/src/TagPool.hxx
new file mode 100644
index 000000000..a6b28b355
--- /dev/null
+++ b/src/TagPool.hxx
@@ -0,0 +1,39 @@
+/*
+ * 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_POOL_HXX
+#define MPD_TAG_POOL_HXX
+
+#include "TagType.h"
+#include "thread/Mutex.hxx"
+
+extern Mutex tag_pool_lock;
+
+struct TagItem;
+
+TagItem *
+tag_pool_get_item(enum tag_type type, const char *value, size_t length);
+
+TagItem *
+tag_pool_dup_item(TagItem *item);
+
+void
+tag_pool_put_item(TagItem *item);
+
+#endif
diff --git a/src/TagPrint.cxx b/src/TagPrint.cxx
new file mode 100644
index 000000000..18490792c
--- /dev/null
+++ b/src/TagPrint.cxx
@@ -0,0 +1,48 @@
+/*
+ * 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 "TagPrint.hxx"
+#include "Tag.hxx"
+#include "TagInternal.hxx"
+#include "Song.hxx"
+#include "Client.hxx"
+
+void tag_print_types(Client *client)
+{
+ int i;
+
+ for (i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++) {
+ if (!ignore_tag_items[i])
+ client_printf(client, "tagtype: %s\n",
+ tag_item_names[i]);
+ }
+}
+
+void tag_print(Client *client, const Tag &tag)
+{
+ if (tag.time >= 0)
+ client_printf(client, SONG_TIME "%i\n", tag.time);
+
+ for (unsigned i = 0; i < tag.num_items; i++) {
+ client_printf(client, "%s: %s\n",
+ tag_item_names[tag.items[i]->type],
+ tag.items[i]->value);
+ }
+}
diff --git a/src/TagPrint.hxx b/src/TagPrint.hxx
new file mode 100644
index 000000000..48bfc72cc
--- /dev/null
+++ b/src/TagPrint.hxx
@@ -0,0 +1,31 @@
+/*
+ * 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_PRINT_HXX
+#define MPD_TAG_PRINT_HXX
+
+struct Tag;
+class Client;
+
+void tag_print_types(Client *client);
+
+void
+tag_print(Client *client, const Tag &tag);
+
+#endif
diff --git a/src/TagRva2.cxx b/src/TagRva2.cxx
new file mode 100644
index 000000000..f41119c35
--- /dev/null
+++ b/src/TagRva2.cxx
@@ -0,0 +1,150 @@
+/*
+ * 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 "TagRva2.hxx"
+#include "replay_gain_info.h"
+
+#include <stdint.h>
+#include <string.h>
+#include <glib.h>
+#include <id3tag.h>
+
+enum rva2_channel {
+ CHANNEL_OTHER = 0x00,
+ CHANNEL_MASTER_VOLUME = 0x01,
+ CHANNEL_FRONT_RIGHT = 0x02,
+ CHANNEL_FRONT_LEFT = 0x03,
+ CHANNEL_BACK_RIGHT = 0x04,
+ CHANNEL_BACK_LEFT = 0x05,
+ CHANNEL_FRONT_CENTRE = 0x06,
+ CHANNEL_BACK_CENTRE = 0x07,
+ CHANNEL_SUBWOOFER = 0x08
+};
+
+struct rva2_data {
+ uint8_t type;
+ uint8_t volume_adjustment[2];
+ uint8_t peak_bits;
+};
+
+static inline id3_length_t
+rva2_peak_bytes(const struct rva2_data *data)
+{
+ return (data->peak_bits + 7) / 8;
+}
+
+static inline int
+rva2_fixed_volume_adjustment(const struct rva2_data *data)
+{
+ signed int voladj_fixed;
+ voladj_fixed = (data->volume_adjustment[0] << 8) |
+ data->volume_adjustment[1];
+ voladj_fixed |= -(voladj_fixed & 0x8000);
+ return voladj_fixed;
+}
+
+static inline float
+rva2_float_volume_adjustment(const struct rva2_data *data)
+{
+ /*
+ * "The volume adjustment is encoded as a fixed point decibel
+ * value, 16 bit signed integer representing (adjustment*512),
+ * giving +/- 64 dB with a precision of 0.001953125 dB."
+ */
+
+ return (float)rva2_fixed_volume_adjustment(data) / (float)512;
+}
+
+static inline bool
+rva2_apply_data(struct replay_gain_info *replay_gain_info,
+ const struct rva2_data *data, const id3_latin1_t *id)
+{
+ if (data->type != CHANNEL_MASTER_VOLUME)
+ return false;
+
+ float volume_adjustment = rva2_float_volume_adjustment(data);
+
+ if (strcmp((const char *)id, "album") == 0) {
+ replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain = volume_adjustment;
+ } else if (strcmp((const char *)id, "track") == 0) {
+ replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain = volume_adjustment;
+ } else {
+ replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain = volume_adjustment;
+ replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain = volume_adjustment;
+ }
+
+ return true;
+}
+
+static bool
+rva2_apply_frame(struct replay_gain_info *replay_gain_info,
+ const struct id3_frame *frame)
+{
+ const id3_latin1_t *id = id3_field_getlatin1(id3_frame_field(frame, 0));
+ id3_length_t length;
+ const id3_byte_t *data =
+ id3_field_getbinarydata(id3_frame_field(frame, 1), &length);
+
+ if (id == nullptr || data == nullptr)
+ return false;
+
+ /*
+ * "The 'identification' string is used to identify the
+ * situation and/or device where this adjustment should apply.
+ * The following is then repeated for every channel
+ *
+ * Type of channel $xx
+ * Volume adjustment $xx xx
+ * Bits representing peak $xx
+ * Peak volume $xx (xx ...)"
+ */
+
+ while (length >= 4) {
+ const struct rva2_data *d = (const struct rva2_data *)data;
+ unsigned int peak_bytes = rva2_peak_bytes(d);
+ if (4 + peak_bytes > length)
+ break;
+
+ if (rva2_apply_data(replay_gain_info, d, id))
+ return true;
+
+ data += 4 + peak_bytes;
+ length -= 4 + peak_bytes;
+ }
+
+ return false;
+}
+
+bool
+tag_rva2_parse(struct id3_tag *tag, struct replay_gain_info *replay_gain_info)
+{
+ bool found = false;
+
+ /* Loop through all RVA2 frames as some programs (e.g. mp3gain) store
+ track and album gain in separate tags */
+ const struct id3_frame *frame;
+ for (unsigned i = 0;
+ (frame = id3_tag_findframe(tag, "RVA2", i)) != nullptr;
+ ++i)
+ if (rva2_apply_frame(replay_gain_info, frame))
+ found = true;
+
+ return found;
+}
diff --git a/src/TagRva2.hxx b/src/TagRva2.hxx
new file mode 100644
index 000000000..016a3585d
--- /dev/null
+++ b/src/TagRva2.hxx
@@ -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.
+ */
+
+#ifndef MPD_TAG_RVA2_HXX
+#define MPD_TAG_RVA2_HXX
+
+#include "check.h"
+
+struct id3_tag;
+struct replay_gain_info;
+
+/**
+ * Parse the RVA2 tag, and fill the #replay_gain_info struct. This is
+ * used by decoder plugins with ID3 support.
+ *
+ * @return true on success
+ */
+bool
+tag_rva2_parse(struct id3_tag *tag, struct replay_gain_info *replay_gain_info);
+
+#endif
diff --git a/src/TagSave.cxx b/src/TagSave.cxx
new file mode 100644
index 000000000..4a9b98a90
--- /dev/null
+++ b/src/TagSave.cxx
@@ -0,0 +1,39 @@
+/*
+ * 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 "TagSave.hxx"
+#include "Tag.hxx"
+#include "TagInternal.hxx"
+#include "Song.hxx"
+
+void
+tag_save(FILE *file, const Tag &tag)
+{
+ if (tag.time >= 0)
+ fprintf(file, SONG_TIME "%i\n", tag.time);
+
+ if (tag.has_playlist)
+ fprintf(file, "Playlist: yes\n");
+
+ for (unsigned i = 0; i < tag.num_items; i++)
+ fprintf(file, "%s: %s\n",
+ tag_item_names[tag.items[i]->type],
+ tag.items[i]->value);
+}
diff --git a/src/TagSave.hxx b/src/TagSave.hxx
new file mode 100644
index 000000000..0b1359c89
--- /dev/null
+++ b/src/TagSave.hxx
@@ -0,0 +1,30 @@
+/*
+ * 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_SAVE_HXX
+#define MPD_TAG_SAVE_HXX
+
+#include <stdio.h>
+
+struct Tag;
+
+void
+tag_save(FILE *file, const Tag &tag);
+
+#endif
diff --git a/src/TagTable.hxx b/src/TagTable.hxx
new file mode 100644
index 000000000..70c02e6e5
--- /dev/null
+++ b/src/TagTable.hxx
@@ -0,0 +1,68 @@
+/*
+ * 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_TABLE_HXX
+#define MPD_TAG_TABLE_HXX
+
+#include "TagType.h"
+#include "gcc.h"
+
+#include <glib.h>
+
+#include <string.h>
+
+struct tag_table {
+ const char *name;
+
+ enum tag_type type;
+};
+
+/**
+ * Looks up a string in a tag translation table (case sensitive).
+ * Returns TAG_NUM_OF_ITEM_TYPES if the specified name was not found
+ * in the table.
+ */
+gcc_pure
+static inline enum tag_type
+tag_table_lookup(const struct tag_table *table, const char *name)
+{
+ for (; table->name != nullptr; ++table)
+ if (strcmp(name, table->name) == 0)
+ return table->type;
+
+ return TAG_NUM_OF_ITEM_TYPES;
+}
+
+/**
+ * Looks up a string in a tag translation table (case insensitive).
+ * Returns TAG_NUM_OF_ITEM_TYPES if the specified name was not found
+ * in the table.
+ */
+gcc_pure
+static inline enum tag_type
+tag_table_lookup_i(const struct tag_table *table, const char *name)
+{
+ for (; table->name != nullptr; ++table)
+ if (g_ascii_strcasecmp(name, table->name) == 0)
+ return table->type;
+
+ return TAG_NUM_OF_ITEM_TYPES;
+}
+
+#endif
diff --git a/src/TagType.h b/src/TagType.h
new file mode 100644
index 000000000..7a1d351a5
--- /dev/null
+++ b/src/TagType.h
@@ -0,0 +1,56 @@
+/*
+ * 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_TYPE_H
+#define MPD_TAG_TYPE_H
+
+/**
+ * Codes for the type of a tag item.
+ */
+enum tag_type {
+ TAG_ARTIST,
+ TAG_ARTIST_SORT,
+ TAG_ALBUM,
+ TAG_ALBUM_ARTIST,
+ TAG_ALBUM_ARTIST_SORT,
+ TAG_TITLE,
+ TAG_TRACK,
+ TAG_NAME,
+ TAG_GENRE,
+ TAG_DATE,
+ TAG_COMPOSER,
+ TAG_PERFORMER,
+ TAG_COMMENT,
+ TAG_DISC,
+
+ TAG_MUSICBRAINZ_ARTISTID,
+ TAG_MUSICBRAINZ_ALBUMID,
+ TAG_MUSICBRAINZ_ALBUMARTISTID,
+ TAG_MUSICBRAINZ_TRACKID,
+
+ TAG_NUM_OF_ITEM_TYPES
+};
+
+/**
+ * An array of strings, which map the #tag_type to its machine
+ * readable name (specific to the MPD protocol).
+ */
+extern const char *tag_item_names[];
+
+#endif
diff --git a/src/TextFile.cxx b/src/TextFile.cxx
new file mode 100644
index 000000000..4ad59ee4a
--- /dev/null
+++ b/src/TextFile.cxx
@@ -0,0 +1,61 @@
+/*
+ * 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 "TextFile.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+char *
+TextFile::ReadLine()
+{
+ gsize length = 0, i;
+ char *p;
+
+ assert(file != NULL);
+ assert(buffer != NULL);
+ assert(buffer->allocated_len >= step);
+
+ while (buffer->len < max_length) {
+ p = fgets(buffer->str + length,
+ buffer->allocated_len - length, file);
+ if (p == NULL) {
+ if (length == 0 || ferror(file))
+ return NULL;
+ break;
+ }
+
+ i = strlen(buffer->str + length);
+ length += i;
+ if (i < step - 1 || buffer->str[length - 1] == '\n')
+ break;
+
+ g_string_set_size(buffer, length + step);
+ }
+
+ /* remove the newline characters */
+ if (buffer->str[length - 1] == '\n')
+ --length;
+ if (buffer->str[length - 1] == '\r')
+ --length;
+
+ g_string_set_size(buffer, length);
+ return buffer->str;
+}
diff --git a/src/TextFile.hxx b/src/TextFile.hxx
new file mode 100644
index 000000000..d593e0961
--- /dev/null
+++ b/src/TextFile.hxx
@@ -0,0 +1,67 @@
+/*
+ * 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_TEXT_FILE_HXX
+#define MPD_TEXT_FILE_HXX
+
+#include "gcc.h"
+#include "fs/Path.hxx"
+#include "fs/FileSystem.hxx"
+
+#include <glib.h>
+
+class TextFile {
+ static constexpr size_t max_length = 512 * 1024;
+ static constexpr size_t step = 1024;
+
+ FILE *const file;
+
+ GString *const buffer;
+
+public:
+ TextFile(const Path &path_fs)
+ :file(FOpen(path_fs, FOpenMode::ReadText)),
+ buffer(g_string_sized_new(step)) {}
+
+ TextFile(const TextFile &other) = delete;
+
+ ~TextFile() {
+ if (file != nullptr)
+ fclose(file);
+
+ g_string_free(buffer, true);
+ }
+
+ bool HasFailed() const {
+ return gcc_unlikely(file == nullptr);
+ }
+
+ /**
+ * Reads a line from the input file, and strips trailing
+ * space. There is a reasonable maximum line length, only to
+ * prevent denial of service.
+ *
+ * @param file the source file, opened in text mode
+ * @param buffer an allocator for the buffer
+ * @return a pointer to the line, or NULL on end-of-file or error
+ */
+ char *ReadLine();
+};
+
+#endif
diff --git a/src/TextInputStream.cxx b/src/TextInputStream.cxx
new file mode 100644
index 000000000..5e963ae01
--- /dev/null
+++ b/src/TextInputStream.cxx
@@ -0,0 +1,92 @@
+/*
+ * 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.
+ */
+
+#include "config.h"
+#include "TextInputStream.hxx"
+#include "input_stream.h"
+#include "util/fifo_buffer.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+
+TextInputStream::TextInputStream(struct input_stream *_is)
+ : is(_is),
+ buffer(fifo_buffer_new(4096))
+{
+}
+
+TextInputStream::~TextInputStream()
+{
+ fifo_buffer_free(buffer);
+}
+
+bool TextInputStream::ReadLine(std::string &line)
+{
+ GError *error = nullptr;
+ void *dest;
+ const char *src, *p;
+ size_t length, nbytes;
+
+ do {
+ dest = fifo_buffer_write(buffer, &length);
+ if (dest != nullptr && length >= 2) {
+ /* reserve one byte for the null terminator if
+ the last line is not terminated by a
+ newline character */
+ --length;
+
+ nbytes = input_stream_lock_read(is, dest, length,
+ &error);
+ if (nbytes > 0)
+ fifo_buffer_append(buffer, nbytes);
+ else if (error != nullptr) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return false;
+ }
+ } else
+ nbytes = 0;
+
+ auto src_p = fifo_buffer_read(buffer, &length);
+ src = reinterpret_cast<const char *>(src_p);
+
+ if (src == nullptr)
+ return false;
+
+ p = reinterpret_cast<const char*>(memchr(src, '\n', length));
+ if (p == nullptr && nbytes == 0) {
+ /* end of file (or line too long): terminate
+ the current line */
+ dest = fifo_buffer_write(buffer, &nbytes);
+ assert(dest != nullptr);
+ *(char *)dest = '\n';
+ fifo_buffer_append(buffer, 1);
+ }
+ } while (p == nullptr);
+
+ length = p - src + 1;
+ while (p > src && g_ascii_isspace(p[-1]))
+ --p;
+
+ line = std::string(src, p - src);
+ fifo_buffer_consume(buffer, length);
+ return true;
+}
diff --git a/src/TextInputStream.hxx b/src/TextInputStream.hxx
new file mode 100644
index 000000000..2608184e2
--- /dev/null
+++ b/src/TextInputStream.hxx
@@ -0,0 +1,59 @@
+/*
+ * 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_TEXT_INPUT_STREAM_HXX
+#define MPD_TEXT_INPUT_STREAM_HXX
+
+#include <string>
+
+struct input_stream;
+struct fifo_buffer;
+
+class TextInputStream {
+ struct input_stream *is;
+ struct fifo_buffer *buffer;
+public:
+ /**
+ * Wraps an existing #input_stream object into a #TextInputStream,
+ * to read its contents as text lines.
+ *
+ * @param _is an open #input_stream object
+ */
+ explicit TextInputStream(struct input_stream *_is);
+
+ /**
+ * Frees the #TextInputStream object. Does not close or free the
+ * underlying #input_stream.
+ */
+ ~TextInputStream();
+
+ TextInputStream(const TextInputStream &) = delete;
+ TextInputStream& operator=(const TextInputStream &) = delete;
+
+ /**
+ * Reads the next line from the stream with newline character stripped.
+ *
+ * @param line a string to put result to
+ * @return true if line is read successfully, false on end of file
+ * or error
+ */
+ bool ReadLine(std::string &line);
+};
+
+#endif
diff --git a/src/TimePrint.cxx b/src/TimePrint.cxx
new file mode 100644
index 000000000..c5247dd9d
--- /dev/null
+++ b/src/TimePrint.cxx
@@ -0,0 +1,47 @@
+/*
+ * 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 "TimePrint.hxx"
+#include "Client.hxx"
+
+#include <glib.h>
+
+void
+time_print(Client *client, const char *name, time_t t)
+{
+#ifdef G_OS_WIN32
+ const struct tm *tm2 = gmtime(&t);
+#else
+ struct tm tm;
+ const struct tm *tm2 = gmtime_r(&t, &tm);
+#endif
+ if (tm2 == NULL)
+ return;
+
+ char buffer[32];
+ strftime(buffer, sizeof(buffer),
+#ifdef G_OS_WIN32
+ "%Y-%m-%dT%H:%M:%SZ",
+#else
+ "%FT%TZ",
+#endif
+ tm2);
+ client_printf(client, "%s: %s\n", name, buffer);
+}
diff --git a/src/TimePrint.hxx b/src/TimePrint.hxx
new file mode 100644
index 000000000..f45101256
--- /dev/null
+++ b/src/TimePrint.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_TIME_PRINT_HXX
+#define MPD_TIME_PRINT_HXX
+
+#include <time.h>
+
+class Client;
+
+/**
+ * Write a line with a time stamp to the client.
+ */
+void
+time_print(Client *client, const char *name, time_t t);
+
+#endif
diff --git a/src/Timer.cxx b/src/Timer.cxx
new file mode 100644
index 000000000..18ccffbcb
--- /dev/null
+++ b/src/Timer.cxx
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+
+#include "config.h"
+#include "Timer.hxx"
+#include "AudioFormat.hxx"
+#include "clock.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <limits.h>
+#include <stddef.h>
+
+Timer::Timer(const AudioFormat af)
+ : time(0),
+ started(false),
+ rate(af.sample_rate * af.GetFrameSize())
+{
+}
+
+void Timer::Start()
+{
+ time = monotonic_clock_us();
+ started = true;
+}
+
+void Timer::Reset()
+{
+ time = 0;
+ started = false;
+}
+
+void Timer::Add(int size)
+{
+ assert(started);
+
+ // (size samples) / (rate samples per second) = duration seconds
+ // duration seconds * 1000000 = duration us
+ time += ((uint64_t)size * 1000000) / rate;
+}
+
+unsigned Timer::GetDelay() const
+{
+ int64_t delay = (int64_t)(time - monotonic_clock_us()) / 1000;
+ if (delay < 0)
+ return 0;
+
+ if (delay > G_MAXINT)
+ delay = G_MAXINT;
+
+ return delay;
+}
+
+void Timer::Synchronize() const
+{
+ int64_t sleep_duration;
+
+ assert(started);
+
+ sleep_duration = time - monotonic_clock_us();
+ if (sleep_duration > 0)
+ g_usleep(sleep_duration);
+}
diff --git a/src/Timer.hxx b/src/Timer.hxx
new file mode 100644
index 000000000..ccc29b873
--- /dev/null
+++ b/src/Timer.hxx
@@ -0,0 +1,49 @@
+/*
+ * 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_TIMER_HXX
+#define MPD_TIMER_HXX
+
+#include <stdint.h>
+
+struct AudioFormat;
+
+class Timer {
+ uint64_t time;
+ bool started;
+ const int rate;
+public:
+ explicit Timer(AudioFormat af);
+
+ bool IsStarted() const { return started; }
+
+ void Start();
+ void Reset();
+
+ void Add(int size);
+
+ /**
+ * Returns the number of milliseconds to sleep to get back to sync.
+ */
+ unsigned GetDelay() const;
+
+ void Synchronize() const;
+};
+
+#endif
diff --git a/src/UpdateArchive.cxx b/src/UpdateArchive.cxx
new file mode 100644
index 000000000..ab174fa53
--- /dev/null
+++ b/src/UpdateArchive.cxx
@@ -0,0 +1,165 @@
+/*
+ * 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" /* must be first for large file support */
+#include "UpdateArchive.hxx"
+#include "UpdateInternal.hxx"
+#include "DatabaseLock.hxx"
+#include "Directory.hxx"
+#include "Song.hxx"
+#include "Mapper.hxx"
+#include "fs/Path.hxx"
+#include "ArchiveList.hxx"
+#include "ArchivePlugin.hxx"
+#include "ArchiveFile.hxx"
+#include "ArchiveVisitor.hxx"
+
+#include <glib.h>
+
+#include <string.h>
+
+static void
+update_archive_tree(Directory *directory, const char *name)
+{
+ const char *tmp = strchr(name, '/');
+ if (tmp) {
+ char *child_name = g_strndup(name, tmp - name);
+ //add dir is not there already
+ db_lock();
+ Directory *subdir =
+ directory->MakeChild(child_name);
+ subdir->device = DEVICE_INARCHIVE;
+ db_unlock();
+ g_free(child_name);
+
+ //create directories first
+ update_archive_tree(subdir, tmp+1);
+ } else {
+ if (strlen(name) == 0) {
+ g_warning("archive returned directory only");
+ return;
+ }
+
+ //add file
+ db_lock();
+ Song *song = directory->FindSong(name);
+ db_unlock();
+ if (song == NULL) {
+ song = Song::LoadFile(name, directory);
+ if (song != NULL) {
+ db_lock();
+ directory->AddSong(song);
+ db_unlock();
+
+ modified = true;
+ g_message("added %s/%s",
+ directory->GetPath(), name);
+ }
+ }
+ }
+}
+
+/**
+ * Updates the file listing from an archive file.
+ *
+ * @param parent the parent directory the archive file resides in
+ * @param name the UTF-8 encoded base name of the archive file
+ * @param st stat() information on the archive file
+ * @param plugin the archive plugin which fits this archive type
+ */
+static void
+update_archive_file2(Directory *parent, const char *name,
+ const struct stat *st,
+ const struct archive_plugin *plugin)
+{
+ db_lock();
+ Directory *directory = parent->FindChild(name);
+ db_unlock();
+
+ if (directory != NULL && directory->mtime == st->st_mtime &&
+ !walk_discard)
+ /* MPD has already scanned the archive, and it hasn't
+ changed since - don't consider updating it */
+ return;
+
+ const Path path_fs = map_directory_child_fs(parent, name);
+
+ /* open archive */
+ GError *error = NULL;
+ ArchiveFile *file = archive_file_open(plugin, path_fs.c_str(), &error);
+ if (file == NULL) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return;
+ }
+
+ g_debug("archive %s opened", path_fs.c_str());
+
+ if (directory == NULL) {
+ g_debug("creating archive directory: %s", name);
+ db_lock();
+ directory = parent->CreateChild(name);
+ /* mark this directory as archive (we use device for
+ this) */
+ directory->device = DEVICE_INARCHIVE;
+ db_unlock();
+ }
+
+ directory->mtime = st->st_mtime;
+
+ class UpdateArchiveVisitor final : public ArchiveVisitor {
+ Directory *directory;
+
+ public:
+ UpdateArchiveVisitor(Directory *_directory)
+ :directory(_directory) {}
+
+ virtual void VisitArchiveEntry(const char *path_utf8) override {
+ g_debug("adding archive file: %s", path_utf8);
+ update_archive_tree(directory, path_utf8);
+ }
+ };
+
+ UpdateArchiveVisitor visitor(directory);
+ file->Visit(visitor);
+ file->Close();
+}
+
+bool
+update_archive_file(Directory *directory,
+ const char *name, const char *suffix,
+ const struct stat *st)
+{
+#ifdef ENABLE_ARCHIVE
+ const struct archive_plugin *plugin =
+ archive_plugin_from_suffix(suffix);
+ if (plugin == NULL)
+ return false;
+
+ update_archive_file2(directory, name, st, plugin);
+ return true;
+#else
+ (void)directory;
+ (void)name;
+ (void)suffix;
+ (void)st;
+
+ return false;
+#endif
+}
diff --git a/src/UpdateArchive.hxx b/src/UpdateArchive.hxx
new file mode 100644
index 000000000..73b363d27
--- /dev/null
+++ b/src/UpdateArchive.hxx
@@ -0,0 +1,51 @@
+/*
+ * 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_UPDATE_ARCHIVE_HXX
+#define MPD_UPDATE_ARCHIVE_HXX
+
+#include "check.h"
+#include "gcc.h"
+
+#include <sys/stat.h>
+
+struct Directory;
+struct archive_plugin;
+
+#ifdef ENABLE_ARCHIVE
+
+bool
+update_archive_file(Directory *directory,
+ const char *name, const char *suffix,
+ const struct stat *st);
+
+#else
+
+static inline bool
+update_archive_file(gcc_unused Directory *directory,
+ gcc_unused const char *name,
+ gcc_unused const char *suffix,
+ gcc_unused const struct stat *st)
+{
+ return false;
+}
+
+#endif
+
+#endif
diff --git a/src/UpdateContainer.cxx b/src/UpdateContainer.cxx
new file mode 100644
index 000000000..44c961a2b
--- /dev/null
+++ b/src/UpdateContainer.cxx
@@ -0,0 +1,119 @@
+/*
+ * 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" /* must be first for large file support */
+#include "UpdateContainer.hxx"
+#include "UpdateInternal.hxx"
+#include "UpdateDatabase.hxx"
+#include "DatabaseLock.hxx"
+#include "Directory.hxx"
+#include "Song.hxx"
+#include "DecoderPlugin.hxx"
+#include "Mapper.hxx"
+#include "fs/Path.hxx"
+#include "TagHandler.hxx"
+#include "Tag.hxx"
+
+#include <glib.h>
+
+/**
+ * Create the specified directory object if it does not exist already
+ * or if the #stat object indicates that it has been modified since
+ * the last update. Returns NULL when it exists already and is
+ * unmodified.
+ *
+ * The caller must lock the database.
+ */
+static Directory *
+make_directory_if_modified(Directory *parent, const char *name,
+ const struct stat *st)
+{
+ Directory *directory = parent->FindChild(name);
+
+ // directory exists already
+ if (directory != NULL) {
+ if (directory->mtime == st->st_mtime && !walk_discard) {
+ /* not modified */
+ return NULL;
+ }
+
+ delete_directory(directory);
+ modified = true;
+ }
+
+ directory = parent->MakeChild(name);
+ directory->mtime = st->st_mtime;
+ return directory;
+}
+
+bool
+update_container_file(Directory *directory,
+ const char *name,
+ const struct stat *st,
+ const struct decoder_plugin *plugin)
+{
+ if (plugin->container_scan == NULL)
+ return false;
+
+ db_lock();
+ Directory *contdir = make_directory_if_modified(directory, name, st);
+ if (contdir == NULL) {
+ /* not modified */
+ db_unlock();
+ return true;
+ }
+
+ contdir->device = DEVICE_CONTAINER;
+ db_unlock();
+
+ const Path pathname = map_directory_child_fs(directory, name);
+
+ char *vtrack;
+ unsigned int tnum = 0;
+ while ((vtrack = plugin->container_scan(pathname.c_str(), ++tnum)) != NULL) {
+ Song *song = Song::NewFile(vtrack, contdir);
+
+ // shouldn't be necessary but it's there..
+ song->mtime = st->st_mtime;
+
+ const Path child_path_fs =
+ map_directory_child_fs(contdir, vtrack);
+
+ song->tag = new Tag();
+ decoder_plugin_scan_file(plugin, child_path_fs.c_str(),
+ &add_tag_handler, song->tag);
+
+ db_lock();
+ contdir->AddSong(song);
+ db_unlock();
+
+ modified = true;
+
+ g_message("added %s/%s", directory->GetPath(), vtrack);
+ g_free(vtrack);
+ }
+
+ if (tnum == 1) {
+ db_lock();
+ delete_directory(contdir);
+ db_unlock();
+ return false;
+ } else
+ return true;
+}
diff --git a/src/UpdateContainer.hxx b/src/UpdateContainer.hxx
new file mode 100644
index 000000000..0078730d6
--- /dev/null
+++ b/src/UpdateContainer.hxx
@@ -0,0 +1,36 @@
+/*
+ * 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_UPDATE_CONTAINER_HXX
+#define MPD_UPDATE_CONTAINER_HXX
+
+#include "check.h"
+
+#include <sys/stat.h>
+
+struct Directory;
+struct decoder_plugin;
+
+bool
+update_container_file(Directory *directory,
+ const char *name,
+ const struct stat *st,
+ const struct decoder_plugin *plugin);
+
+#endif
diff --git a/src/UpdateDatabase.cxx b/src/UpdateDatabase.cxx
new file mode 100644
index 000000000..9d2fa9017
--- /dev/null
+++ b/src/UpdateDatabase.cxx
@@ -0,0 +1,104 @@
+/*
+ * 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" /* must be first for large file support */
+#include "UpdateDatabase.hxx"
+#include "UpdateRemove.hxx"
+#include "PlaylistVector.hxx"
+#include "Directory.hxx"
+#include "Song.hxx"
+#include "DatabaseLock.hxx"
+
+#include <glib.h>
+#include <assert.h>
+
+void
+delete_song(Directory *dir, Song *del)
+{
+ assert(del->parent == dir);
+
+ /* first, prevent traversers in main task from getting this */
+ dir->RemoveSong(del);
+
+ db_unlock(); /* temporary unlock, because update_remove_song() blocks */
+
+ /* now take it out of the playlist (in the main_task) */
+ update_remove_song(del);
+
+ /* finally, all possible references gone, free it */
+ del->Free();
+
+ db_lock();
+}
+
+/**
+ * Recursively remove all sub directories and songs from a directory,
+ * leaving an empty directory.
+ *
+ * Caller must lock the #db_mutex.
+ */
+static void
+clear_directory(Directory *directory)
+{
+ Directory *child, *n;
+ directory_for_each_child_safe(child, n, directory)
+ delete_directory(child);
+
+ Song *song, *ns;
+ directory_for_each_song_safe(song, ns, directory) {
+ assert(song->parent == directory);
+ delete_song(directory, song);
+ }
+}
+
+void
+delete_directory(Directory *directory)
+{
+ assert(directory->parent != NULL);
+
+ clear_directory(directory);
+
+ directory->Delete();
+}
+
+bool
+delete_name_in(Directory *parent, const char *name)
+{
+ bool modified = false;
+
+ db_lock();
+ Directory *directory = parent->FindChild(name);
+
+ if (directory != NULL) {
+ delete_directory(directory);
+ modified = true;
+ }
+
+ Song *song = parent->FindSong(name);
+ if (song != NULL) {
+ delete_song(parent, song);
+ modified = true;
+ }
+
+ parent->playlists.erase(name);
+
+ db_unlock();
+
+ return modified;
+}
diff --git a/src/UpdateDatabase.hxx b/src/UpdateDatabase.hxx
new file mode 100644
index 000000000..ab8f7ec26
--- /dev/null
+++ b/src/UpdateDatabase.hxx
@@ -0,0 +1,50 @@
+/*
+ * 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_UPDATE_DATABASE_HXX
+#define MPD_UPDATE_DATABASE_HXX
+
+#include "check.h"
+
+struct Directory;
+struct Song;
+
+/**
+ * Caller must lock the #db_mutex.
+ */
+void
+delete_song(Directory *parent, Song *song);
+
+/**
+ * Recursively free a directory and all its contents.
+ *
+ * Caller must lock the #db_mutex.
+ */
+void
+delete_directory(Directory *directory);
+
+/**
+ * Caller must NOT lock the #db_mutex.
+ *
+ * @return true if the database was modified
+ */
+bool
+delete_name_in(Directory *parent, const char *name);
+
+#endif
diff --git a/src/UpdateGlue.cxx b/src/UpdateGlue.cxx
new file mode 100644
index 000000000..5c63efcda
--- /dev/null
+++ b/src/UpdateGlue.cxx
@@ -0,0 +1,186 @@
+/*
+ * 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 "UpdateGlue.hxx"
+#include "UpdateQueue.hxx"
+#include "UpdateWalk.hxx"
+#include "UpdateRemove.hxx"
+#include "Mapper.hxx"
+#include "DatabaseSimple.hxx"
+#include "Idle.hxx"
+#include "GlobalEvents.hxx"
+
+extern "C" {
+#include "stats.h"
+}
+
+#include "Main.hxx"
+#include "Instance.hxx"
+#include "mpd_error.h"
+
+#include <glib.h>
+
+#include <assert.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "update"
+
+static enum update_progress {
+ UPDATE_PROGRESS_IDLE = 0,
+ UPDATE_PROGRESS_RUNNING = 1,
+ UPDATE_PROGRESS_DONE = 2
+} progress;
+
+static bool modified;
+
+static GThread *update_thr;
+
+static const unsigned update_task_id_max = 1 << 15;
+
+static unsigned update_task_id;
+
+/* XXX this flag is passed to update_task() */
+static bool discard;
+
+unsigned
+isUpdatingDB(void)
+{
+ return (progress != UPDATE_PROGRESS_IDLE) ? update_task_id : 0;
+}
+
+static void * update_task(void *_path)
+{
+ const char *path = (const char *)_path;
+
+ if (path != NULL && *path != 0)
+ g_debug("starting: %s", path);
+ else
+ g_debug("starting");
+
+ modified = update_walk(path, discard);
+
+ if (modified || !db_exists()) {
+ GError *error = NULL;
+ if (!db_save(&error)) {
+ g_warning("Failed to save database: %s",
+ error->message);
+ g_error_free(error);
+ }
+ }
+
+ if (path != NULL && *path != 0)
+ g_debug("finished: %s", path);
+ else
+ g_debug("finished");
+ g_free(_path);
+
+ progress = UPDATE_PROGRESS_DONE;
+ GlobalEvents::Emit(GlobalEvents::UPDATE);
+ return NULL;
+}
+
+static void
+spawn_update_task(const char *path)
+{
+ assert(g_thread_self() == main_task);
+
+ progress = UPDATE_PROGRESS_RUNNING;
+ modified = false;
+
+#if GLIB_CHECK_VERSION(2,32,0)
+ update_thr = g_thread_new("updadte", update_task, g_strdup(path));
+#else
+ GError *e = NULL;
+ update_thr = g_thread_create(update_task, g_strdup(path), TRUE, &e);
+ if (update_thr == NULL)
+ MPD_ERROR("Failed to spawn update task: %s", e->message);
+#endif
+
+ if (++update_task_id > update_task_id_max)
+ update_task_id = 1;
+ g_debug("spawned thread for update job id %i", update_task_id);
+}
+
+unsigned
+update_enqueue(const char *path, bool _discard)
+{
+ assert(g_thread_self() == main_task);
+
+ if (!db_is_simple() || !mapper_has_music_directory())
+ return 0;
+
+ if (progress != UPDATE_PROGRESS_IDLE) {
+ unsigned next_task_id =
+ update_queue_push(path, discard, update_task_id);
+ if (next_task_id == 0)
+ return 0;
+
+ return next_task_id > update_task_id_max ? 1 : next_task_id;
+ }
+
+ discard = _discard;
+ spawn_update_task(path);
+
+ idle_add(IDLE_UPDATE);
+
+ return update_task_id;
+}
+
+/**
+ * Called in the main thread after the database update is finished.
+ */
+static void update_finished_event(void)
+{
+ char *path;
+
+ assert(progress == UPDATE_PROGRESS_DONE);
+
+ g_thread_join(update_thr);
+
+ idle_add(IDLE_UPDATE);
+
+ if (modified)
+ /* send "idle" events */
+ instance->DatabaseModified();
+
+ path = update_queue_shift(&discard);
+ if (path != NULL) {
+ /* schedule the next path */
+ spawn_update_task(path);
+ g_free(path);
+ } else {
+ progress = UPDATE_PROGRESS_IDLE;
+
+ stats_update();
+ }
+}
+
+void update_global_init(void)
+{
+ GlobalEvents::Register(GlobalEvents::UPDATE, update_finished_event);
+
+ update_remove_global_init();
+ update_walk_global_init();
+}
+
+void update_global_finish(void)
+{
+ update_walk_global_finish();
+}
diff --git a/src/UpdateGlue.hxx b/src/UpdateGlue.hxx
new file mode 100644
index 000000000..9d546a2a3
--- /dev/null
+++ b/src/UpdateGlue.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_UPDATE_GLUE_HXX
+#define MPD_UPDATE_GLUE_HXX
+
+void update_global_init(void);
+
+void update_global_finish(void);
+
+unsigned
+isUpdatingDB(void);
+
+/**
+ * Add this path to the database update queue.
+ *
+ * @param path a path to update; if NULL or an empty string,
+ * the whole music directory is updated
+ * @return the job id, or 0 on error
+ */
+unsigned
+update_enqueue(const char *path, bool discard);
+
+#endif
diff --git a/src/UpdateIO.cxx b/src/UpdateIO.cxx
new file mode 100644
index 000000000..7e262555c
--- /dev/null
+++ b/src/UpdateIO.cxx
@@ -0,0 +1,113 @@
+/*
+ * 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" /* must be first for large file support */
+#include "UpdateIO.hxx"
+#include "Directory.hxx"
+#include "Mapper.hxx"
+#include "fs/Path.hxx"
+#include "fs/FileSystem.hxx"
+
+#include <glib.h>
+
+#include <errno.h>
+#include <unistd.h>
+
+int
+stat_directory(const Directory *directory, struct stat *st)
+{
+ const Path path_fs = map_directory_fs(directory);
+ if (path_fs.IsNull())
+ return -1;
+
+ if (!StatFile(path_fs, *st)) {
+ int error = errno;
+ const std::string path_utf8 = path_fs.ToUTF8();
+ g_warning("Failed to stat %s: %s",
+ path_utf8.c_str(), g_strerror(error));
+ return -1;
+ }
+
+ return 0;
+}
+
+int
+stat_directory_child(const Directory *parent, const char *name,
+ struct stat *st)
+{
+ const Path path_fs = map_directory_child_fs(parent, name);
+ if (path_fs.IsNull())
+ return -1;
+
+ if (!StatFile(path_fs, *st)) {
+ int error = errno;
+ const std::string path_utf8 = path_fs.ToUTF8();
+ g_warning("Failed to stat %s: %s",
+ path_utf8.c_str(), g_strerror(error));
+ return -1;
+ }
+
+ return 0;
+}
+
+bool
+directory_exists(const Directory *directory)
+{
+ const Path path_fs = map_directory_fs(directory);
+ if (path_fs.IsNull())
+ /* invalid path: cannot exist */
+ return false;
+
+ return directory->device == DEVICE_INARCHIVE ||
+ directory->device == DEVICE_CONTAINER
+ ? FileExists(path_fs)
+ : DirectoryExists(path_fs);
+}
+
+bool
+directory_child_is_regular(const Directory *directory,
+ const char *name_utf8)
+{
+ const Path path_fs = map_directory_child_fs(directory, name_utf8);
+ if (path_fs.IsNull())
+ return false;
+
+ return FileExists(path_fs);
+}
+
+bool
+directory_child_access(const Directory *directory,
+ const char *name, int mode)
+{
+#ifdef WIN32
+ /* CheckAccess() is useless on WIN32 */
+ (void)directory;
+ (void)name;
+ (void)mode;
+ return true;
+#else
+ const Path path = map_directory_child_fs(directory, name);
+ if (path.IsNull())
+ /* something went wrong, but that isn't a permission
+ problem */
+ return true;
+
+ return CheckAccess(path, mode) || errno != EACCES;
+#endif
+}
diff --git a/src/UpdateIO.hxx b/src/UpdateIO.hxx
new file mode 100644
index 000000000..ee47b2682
--- /dev/null
+++ b/src/UpdateIO.hxx
@@ -0,0 +1,50 @@
+/*
+ * 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_UPDATE_IO_HXX
+#define MPD_UPDATE_IO_HXX
+
+#include "check.h"
+
+#include <sys/stat.h>
+
+struct Directory;
+
+int
+stat_directory(const Directory *directory, struct stat *st);
+
+int
+stat_directory_child(const Directory *parent, const char *name,
+ struct stat *st);
+
+bool
+directory_exists(const Directory *directory);
+
+bool
+directory_child_is_regular(const Directory *directory,
+ const char *name_utf8);
+
+/**
+ * Checks if the given permissions on the mapped file are given.
+ */
+bool
+directory_child_access(const Directory *directory,
+ const char *name, int mode);
+
+#endif
diff --git a/src/UpdateInternal.hxx b/src/UpdateInternal.hxx
new file mode 100644
index 000000000..50443c086
--- /dev/null
+++ b/src/UpdateInternal.hxx
@@ -0,0 +1,28 @@
+/*
+ * 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_UPDATE_INTERNAL_H
+#define MPD_UPDATE_INTERNAL_H
+
+#include "check.h"
+
+extern bool walk_discard;
+extern bool modified;
+
+#endif
diff --git a/src/UpdateQueue.cxx b/src/UpdateQueue.cxx
new file mode 100644
index 000000000..85eb22358
--- /dev/null
+++ b/src/UpdateQueue.cxx
@@ -0,0 +1,66 @@
+/*
+ * 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 "UpdateQueue.hxx"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+
+/* make this dynamic?, or maybe this is big enough... */
+static struct {
+ char *path;
+ bool discard;
+} update_queue[32];
+
+static size_t update_queue_length;
+
+unsigned
+update_queue_push(const char *path, bool discard, unsigned base)
+{
+ assert(update_queue_length <= G_N_ELEMENTS(update_queue));
+
+ if (update_queue_length == G_N_ELEMENTS(update_queue))
+ return 0;
+
+ update_queue[update_queue_length].path = g_strdup(path);
+ update_queue[update_queue_length].discard = discard;
+
+ ++update_queue_length;
+
+ return base + update_queue_length;
+}
+
+char *
+update_queue_shift(bool *discard_r)
+{
+ char *path;
+
+ if (update_queue_length == 0)
+ return NULL;
+
+ path = update_queue[0].path;
+ *discard_r = update_queue[0].discard;
+
+ memmove(&update_queue[0], &update_queue[1],
+ --update_queue_length * sizeof(update_queue[0]));
+ return path;
+}
diff --git a/src/UpdateQueue.hxx b/src/UpdateQueue.hxx
new file mode 100644
index 000000000..7de06964f
--- /dev/null
+++ b/src/UpdateQueue.hxx
@@ -0,0 +1,31 @@
+/*
+ * 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_UPDATE_QUEUE_HXX
+#define MPD_UPDATE_QUEUE_HXX
+
+#include "check.h"
+
+unsigned
+update_queue_push(const char *path, bool discard, unsigned base);
+
+char *
+update_queue_shift(bool *discard_r);
+
+#endif
diff --git a/src/UpdateRemove.cxx b/src/UpdateRemove.cxx
new file mode 100644
index 000000000..f9f6994b0
--- /dev/null
+++ b/src/UpdateRemove.cxx
@@ -0,0 +1,96 @@
+/*
+ * 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.
+ */
+
+#include "config.h" /* must be first for large file support */
+#include "UpdateRemove.hxx"
+#include "Playlist.hxx"
+#include "GlobalEvents.hxx"
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+
+#include "Song.hxx"
+#include "Main.hxx"
+#include "Instance.hxx"
+
+#ifdef ENABLE_SQLITE
+#include "StickerDatabase.hxx"
+#include "SongSticker.hxx"
+#endif
+
+#include <glib.h>
+
+#include <assert.h>
+
+static const Song *removed_song;
+
+static Mutex remove_mutex;
+static Cond remove_cond;
+
+/**
+ * Safely remove a song from the database. This must be done in the
+ * main task, to be sure that there is no pointer left to it.
+ */
+static void
+song_remove_event(void)
+{
+ char *uri;
+
+ assert(removed_song != NULL);
+
+ uri = removed_song->GetURI();
+ g_message("removing %s", uri);
+ g_free(uri);
+
+#ifdef ENABLE_SQLITE
+ /* if the song has a sticker, remove it */
+ if (sticker_enabled())
+ sticker_song_delete(removed_song);
+#endif
+
+ instance->DeleteSong(*removed_song);
+
+ /* clear "removed_song" and send signal to update thread */
+ remove_mutex.lock();
+ removed_song = NULL;
+ remove_cond.signal();
+ remove_mutex.unlock();
+}
+
+void
+update_remove_global_init(void)
+{
+ GlobalEvents::Register(GlobalEvents::DELETE, song_remove_event);
+}
+
+void
+update_remove_song(const Song *song)
+{
+ assert(removed_song == NULL);
+
+ removed_song = song;
+
+ GlobalEvents::Emit(GlobalEvents::DELETE);
+
+ remove_mutex.lock();
+
+ while (removed_song != NULL)
+ remove_cond.wait(remove_mutex);
+
+ remove_mutex.unlock();
+}
diff --git a/src/UpdateRemove.hxx b/src/UpdateRemove.hxx
new file mode 100644
index 000000000..bef27d766
--- /dev/null
+++ b/src/UpdateRemove.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_UPDATE_REMOVE_HXX
+#define MPD_UPDATE_REMOVE_HXX
+
+#include "check.h"
+
+struct Song;
+
+void
+update_remove_global_init(void);
+
+/**
+ * Sends a signal to the main thread which will in turn remove the
+ * song: from the sticker database and from the playlist. This
+ * serialized access is implemented to avoid excessive locking.
+ */
+void
+update_remove_song(const Song *song);
+
+#endif
diff --git a/src/UpdateSong.cxx b/src/UpdateSong.cxx
new file mode 100644
index 000000000..b1f8cac4d
--- /dev/null
+++ b/src/UpdateSong.cxx
@@ -0,0 +1,112 @@
+/*
+ * 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" /* must be first for large file support */
+#include "UpdateSong.hxx"
+#include "UpdateInternal.hxx"
+#include "UpdateIO.hxx"
+#include "UpdateDatabase.hxx"
+#include "UpdateContainer.hxx"
+#include "DatabaseLock.hxx"
+#include "Directory.hxx"
+#include "Song.hxx"
+#include "DecoderPlugin.hxx"
+#include "DecoderList.hxx"
+
+#include <glib.h>
+
+#include <unistd.h>
+
+static void
+update_song_file2(Directory *directory,
+ const char *name, const struct stat *st,
+ const struct decoder_plugin *plugin)
+{
+ db_lock();
+ Song *song = directory->FindSong(name);
+ db_unlock();
+
+ if (!directory_child_access(directory, name, R_OK)) {
+ g_warning("no read permissions on %s/%s",
+ directory->GetPath(), name);
+ if (song != NULL) {
+ db_lock();
+ delete_song(directory, song);
+ db_unlock();
+ }
+
+ return;
+ }
+
+ if (!(song != NULL && st->st_mtime == song->mtime &&
+ !walk_discard) &&
+ update_container_file(directory, name, st, plugin)) {
+ if (song != NULL) {
+ db_lock();
+ delete_song(directory, song);
+ db_unlock();
+ }
+
+ return;
+ }
+
+ if (song == NULL) {
+ g_debug("reading %s/%s", directory->GetPath(), name);
+ song = Song::LoadFile(name, directory);
+ if (song == NULL) {
+ g_debug("ignoring unrecognized file %s/%s",
+ directory->GetPath(), name);
+ return;
+ }
+
+ db_lock();
+ directory->AddSong(song);
+ db_unlock();
+
+ modified = true;
+ g_message("added %s/%s",
+ directory->GetPath(), name);
+ } else if (st->st_mtime != song->mtime || walk_discard) {
+ g_message("updating %s/%s",
+ directory->GetPath(), name);
+ if (!song->UpdateFile()) {
+ g_debug("deleting unrecognized file %s/%s",
+ directory->GetPath(), name);
+ db_lock();
+ delete_song(directory, song);
+ db_unlock();
+ }
+
+ modified = true;
+ }
+}
+
+bool
+update_song_file(Directory *directory,
+ const char *name, const char *suffix,
+ const struct stat *st)
+{
+ const struct decoder_plugin *plugin =
+ decoder_plugin_from_suffix(suffix, nullptr);
+ if (plugin == NULL)
+ return false;
+
+ update_song_file2(directory, name, st, plugin);
+ return true;
+}
diff --git a/src/UpdateSong.hxx b/src/UpdateSong.hxx
new file mode 100644
index 000000000..60a532e3a
--- /dev/null
+++ b/src/UpdateSong.hxx
@@ -0,0 +1,34 @@
+/*
+ * 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_UPDATE_SONG_HXX
+#define MPD_UPDATE_SONG_HXX
+
+#include "check.h"
+
+#include <sys/stat.h>
+
+struct Directory;
+
+bool
+update_song_file(Directory *directory,
+ const char *name, const char *suffix,
+ const struct stat *st);
+
+#endif
diff --git a/src/UpdateWalk.cxx b/src/UpdateWalk.cxx
new file mode 100644
index 000000000..cc2b5743e
--- /dev/null
+++ b/src/UpdateWalk.cxx
@@ -0,0 +1,488 @@
+/*
+ * 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" /* must be first for large file support */
+#include "UpdateWalk.hxx"
+#include "UpdateIO.hxx"
+#include "UpdateDatabase.hxx"
+#include "UpdateSong.hxx"
+#include "UpdateArchive.hxx"
+#include "DatabaseLock.hxx"
+#include "DatabaseSimple.hxx"
+#include "Directory.hxx"
+#include "Song.hxx"
+#include "PlaylistVector.hxx"
+#include "PlaylistRegistry.hxx"
+#include "Mapper.hxx"
+#include "ExcludeList.hxx"
+#include "conf.h"
+#include "fs/Path.hxx"
+#include "fs/FileSystem.hxx"
+#include "fs/DirectoryReader.hxx"
+#include "util/UriUtil.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>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "update"
+
+bool walk_discard;
+bool modified;
+
+#ifndef WIN32
+
+enum {
+ DEFAULT_FOLLOW_INSIDE_SYMLINKS = true,
+ DEFAULT_FOLLOW_OUTSIDE_SYMLINKS = true,
+};
+
+static bool follow_inside_symlinks;
+static bool follow_outside_symlinks;
+
+#endif
+
+void
+update_walk_global_init(void)
+{
+#ifndef WIN32
+ follow_inside_symlinks =
+ config_get_bool(CONF_FOLLOW_INSIDE_SYMLINKS,
+ DEFAULT_FOLLOW_INSIDE_SYMLINKS);
+
+ follow_outside_symlinks =
+ config_get_bool(CONF_FOLLOW_OUTSIDE_SYMLINKS,
+ DEFAULT_FOLLOW_OUTSIDE_SYMLINKS);
+#endif
+}
+
+void
+update_walk_global_finish(void)
+{
+}
+
+static void
+directory_set_stat(Directory *dir, const struct stat *st)
+{
+ dir->inode = st->st_ino;
+ dir->device = st->st_dev;
+ dir->have_stat = true;
+}
+
+static void
+remove_excluded_from_directory(Directory *directory,
+ const ExcludeList &exclude_list)
+{
+ db_lock();
+
+ Directory *child, *n;
+ directory_for_each_child_safe(child, n, directory) {
+ const Path name_fs = Path::FromUTF8(child->GetName());
+
+ if (name_fs.IsNull() || exclude_list.Check(name_fs)) {
+ delete_directory(child);
+ modified = true;
+ }
+ }
+
+ Song *song, *ns;
+ directory_for_each_song_safe(song, ns, directory) {
+ assert(song->parent == directory);
+
+ const Path name_fs = Path::FromUTF8(song->uri);
+ if (name_fs.IsNull() || exclude_list.Check(name_fs)) {
+ delete_song(directory, song);
+ modified = true;
+ }
+ }
+
+ db_unlock();
+}
+
+static void
+purge_deleted_from_directory(Directory *directory)
+{
+ Directory *child, *n;
+ directory_for_each_child_safe(child, n, directory) {
+ if (directory_exists(child))
+ continue;
+
+ db_lock();
+ delete_directory(child);
+ db_unlock();
+
+ modified = true;
+ }
+
+ Song *song, *ns;
+ directory_for_each_song_safe(song, ns, directory) {
+ const Path path = map_song_fs(song);
+ if (path.IsNull() || !FileExists(path)) {
+ db_lock();
+ delete_song(directory, song);
+ db_unlock();
+
+ modified = true;
+ }
+ }
+
+ for (auto i = directory->playlists.begin(),
+ end = directory->playlists.end();
+ i != end;) {
+ if (!directory_child_is_regular(directory, i->name.c_str())) {
+ db_lock();
+ i = directory->playlists.erase(i);
+ db_unlock();
+ } else
+ ++i;
+ }
+}
+
+#ifndef G_OS_WIN32
+static int
+update_directory_stat(Directory *directory)
+{
+ struct stat st;
+ if (stat_directory(directory, &st) < 0)
+ return -1;
+
+ directory_set_stat(directory, &st);
+ return 0;
+}
+#endif
+
+static int
+find_inode_ancestor(Directory *parent, ino_t inode, dev_t device)
+{
+#ifndef G_OS_WIN32
+ while (parent) {
+ if (!parent->have_stat && update_directory_stat(parent) < 0)
+ return -1;
+
+ if (parent->inode == inode && parent->device == device) {
+ g_debug("recursive directory found");
+ return 1;
+ }
+
+ parent = parent->parent;
+ }
+#else
+ (void)parent;
+ (void)inode;
+ (void)device;
+#endif
+
+ return 0;
+}
+
+static bool
+update_playlist_file2(Directory *directory,
+ const char *name, const char *suffix,
+ const struct stat *st)
+{
+ if (!playlist_suffix_supported(suffix))
+ return false;
+
+ PlaylistInfo pi(name, st->st_mtime);
+
+ db_lock();
+ if (directory->playlists.UpdateOrInsert(std::move(pi)))
+ modified = true;
+ db_unlock();
+ return true;
+}
+
+static bool
+update_regular_file(Directory *directory,
+ const char *name, const struct stat *st)
+{
+ const char *suffix = uri_get_suffix(name);
+ if (suffix == NULL)
+ return false;
+
+ return update_song_file(directory, name, suffix, st) ||
+ update_archive_file(directory, name, suffix, st) ||
+ update_playlist_file2(directory, name, suffix, st);
+}
+
+static bool
+update_directory(Directory *directory, const struct stat *st);
+
+static void
+update_directory_child(Directory *directory,
+ const char *name, const struct stat *st)
+{
+ assert(strchr(name, '/') == NULL);
+
+ if (S_ISREG(st->st_mode)) {
+ update_regular_file(directory, name, st);
+ } else if (S_ISDIR(st->st_mode)) {
+ if (find_inode_ancestor(directory, st->st_ino, st->st_dev))
+ return;
+
+ db_lock();
+ Directory *subdir = directory->MakeChild(name);
+ db_unlock();
+
+ assert(directory == subdir->parent);
+
+ if (!update_directory(subdir, st)) {
+ db_lock();
+ delete_directory(subdir);
+ db_unlock();
+ }
+ } else {
+ g_debug("update: %s is not a directory, archive or music", name);
+ }
+}
+
+/* we don't look at "." / ".." nor files with newlines in their name */
+G_GNUC_PURE
+static bool skip_path(const Path &path_fs)
+{
+ const char *path = path_fs.c_str();
+ return (path[0] == '.' && path[1] == 0) ||
+ (path[0] == '.' && path[1] == '.' && path[2] == 0) ||
+ strchr(path, '\n') != NULL;
+}
+
+G_GNUC_PURE
+static bool
+skip_symlink(const Directory *directory, const char *utf8_name)
+{
+#ifndef WIN32
+ const Path path_fs = map_directory_child_fs(directory, utf8_name);
+ if (path_fs.IsNull())
+ return true;
+
+ const Path target = ReadLink(path_fs);
+ if (target.IsNull())
+ /* don't skip if this is not a symlink */
+ return errno != EINVAL;
+
+ if (!follow_inside_symlinks && !follow_outside_symlinks) {
+ /* ignore all symlinks */
+ return true;
+ } else if (follow_inside_symlinks && follow_outside_symlinks) {
+ /* consider all symlinks */
+ return false;
+ }
+
+ const char *target_str = target.c_str();
+
+ if (g_path_is_absolute(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);
+ return relative > target_str
+ ? !follow_inside_symlinks
+ : !follow_outside_symlinks;
+ }
+
+ const char *p = target_str;
+ while (*p == '.') {
+ if (p[1] == '.' && G_IS_DIR_SEPARATOR(p[2])) {
+ /* "../" moves to parent directory */
+ directory = directory->parent;
+ if (directory == NULL) {
+ /* we have moved outside the music
+ directory - skip this symlink
+ if such symlinks are not allowed */
+ return !follow_outside_symlinks;
+ }
+ p += 3;
+ } else if (G_IS_DIR_SEPARATOR(p[1]))
+ /* eliminate "./" */
+ p += 2;
+ else
+ break;
+ }
+
+ /* we are still in the music directory, so this symlink points
+ to a song which is already in the database - skip according
+ to the follow_inside_symlinks param*/
+ return !follow_inside_symlinks;
+#else
+ /* no symlink checking on WIN32 */
+
+ (void)directory;
+ (void)utf8_name;
+
+ return false;
+#endif
+}
+
+static bool
+update_directory(Directory *directory, const struct stat *st)
+{
+ assert(S_ISDIR(st->st_mode));
+
+ directory_set_stat(directory, st);
+
+ const Path path_fs = map_directory_fs(directory);
+ if (path_fs.IsNull())
+ return false;
+
+ DirectoryReader reader(path_fs);
+ if (reader.HasFailed()) {
+ int error = errno;
+ const auto path_utf8 = path_fs.ToUTF8();
+ g_warning("Failed to open directory %s: %s",
+ path_utf8.c_str(), g_strerror(error));
+ return false;
+ }
+
+ ExcludeList exclude_list;
+ exclude_list.LoadFile(Path::Build(path_fs, ".mpdignore"));
+
+ if (!exclude_list.IsEmpty())
+ remove_excluded_from_directory(directory, exclude_list);
+
+ purge_deleted_from_directory(directory);
+
+ while (reader.ReadEntry()) {
+ std::string utf8;
+ struct stat st2;
+
+ const Path entry = reader.GetEntry();
+
+ if (skip_path(entry) || exclude_list.Check(entry))
+ continue;
+
+ utf8 = entry.ToUTF8();
+ if (utf8.empty())
+ continue;
+
+ if (skip_symlink(directory, utf8.c_str())) {
+ modified |= delete_name_in(directory, utf8.c_str());
+ continue;
+ }
+
+ if (stat_directory_child(directory, utf8.c_str(), &st2) == 0)
+ update_directory_child(directory, utf8.c_str(), &st2);
+ else
+ modified |= delete_name_in(directory, utf8.c_str());
+ }
+
+ directory->mtime = st->st_mtime;
+
+ return true;
+}
+
+static Directory *
+directory_make_child_checked(Directory *parent, const char *name_utf8)
+{
+ db_lock();
+ Directory *directory = parent->FindChild(name_utf8);
+ db_unlock();
+
+ if (directory != NULL)
+ return directory;
+
+ struct stat st;
+ if (stat_directory_child(parent, name_utf8, &st) < 0 ||
+ find_inode_ancestor(parent, st.st_ino, st.st_dev))
+ return NULL;
+
+ if (skip_symlink(parent, name_utf8))
+ return NULL;
+
+ /* if we're adding directory paths, make sure to delete filenames
+ with potentially the same name */
+ db_lock();
+ Song *conflicting = parent->FindSong(name_utf8);
+ if (conflicting)
+ delete_song(parent, conflicting);
+
+ directory = parent->CreateChild(name_utf8);
+ db_unlock();
+
+ directory_set_stat(directory, &st);
+ return directory;
+}
+
+static Directory *
+directory_make_uri_parent_checked(const char *uri)
+{
+ Directory *directory = db_get_root();
+ char *duplicated = g_strdup(uri);
+ char *name_utf8 = duplicated, *slash;
+
+ while ((slash = strchr(name_utf8, '/')) != NULL) {
+ *slash = 0;
+
+ if (*name_utf8 == 0)
+ continue;
+
+ directory = directory_make_child_checked(directory, name_utf8);
+ if (directory == NULL)
+ break;
+
+ name_utf8 = slash + 1;
+ }
+
+ g_free(duplicated);
+ return directory;
+}
+
+static void
+update_uri(const char *uri)
+{
+ Directory *parent = directory_make_uri_parent_checked(uri);
+ if (parent == NULL)
+ return;
+
+ char *name = g_path_get_basename(uri);
+
+ struct stat st;
+ if (!skip_symlink(parent, name) &&
+ stat_directory_child(parent, name, &st) == 0)
+ update_directory_child(parent, name, &st);
+ else
+ modified |= delete_name_in(parent, name);
+
+ g_free(name);
+}
+
+bool
+update_walk(const char *path, bool discard)
+{
+ walk_discard = discard;
+ modified = false;
+
+ if (path != NULL && !isRootDirectory(path)) {
+ update_uri(path);
+ } else {
+ Directory *directory = db_get_root();
+ struct stat st;
+
+ if (stat_directory(directory, &st) == 0)
+ update_directory(directory, &st);
+ }
+
+ return modified;
+}
diff --git a/src/UpdateWalk.hxx b/src/UpdateWalk.hxx
new file mode 100644
index 000000000..62c0d0a8e
--- /dev/null
+++ b/src/UpdateWalk.hxx
@@ -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.
+ */
+
+#ifndef MPD_UPDATE_WALK_HXX
+#define MPD_UPDATE_WALK_HXX
+
+#include "check.h"
+
+void
+update_walk_global_init(void);
+
+void
+update_walk_global_finish(void);
+
+/**
+ * Returns true if the database was modified.
+ */
+bool
+update_walk(const char *path, bool discard);
+
+#endif
diff --git a/src/Volume.cxx b/src/Volume.cxx
new file mode 100644
index 000000000..116f4aa18
--- /dev/null
+++ b/src/Volume.cxx
@@ -0,0 +1,138 @@
+/*
+ * 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 "MixerAll.hxx"
+#include "Idle.hxx"
+#include "GlobalEvents.hxx"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <stdlib.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "volume"
+
+#define SW_VOLUME_STATE "sw_volume: "
+
+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;
+
+/**
+ * Handler for #GlobalEvents::MIXER.
+ */
+static void
+mixer_event_callback(void)
+{
+ /* flush the hardware volume cache */
+ last_hardware_volume = -1;
+
+ /* notify clients */
+ 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 != NULL);
+
+ if (last_hardware_volume >= 0 &&
+ g_timer_elapsed(hardware_volume_timer, NULL) < 1.0)
+ /* 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;
+}
+
+static bool software_volume_change(unsigned volume)
+{
+ assert(volume <= 100);
+
+ volume_software_set = volume;
+ mixer_all_set_software_volume(volume);
+
+ return true;
+}
+
+static bool hardware_volume_change(unsigned volume)
+{
+ /* reset the cache */
+ last_hardware_volume = -1;
+
+ return mixer_all_set_volume(volume);
+}
+
+bool volume_level_change(unsigned volume)
+{
+ assert(volume <= 100);
+
+ volume_software_set = volume;
+
+ idle_add(IDLE_MIXER);
+
+ return hardware_volume_change(volume);
+}
+
+bool
+read_sw_volume_state(const char *line)
+{
+ char *end = NULL;
+ long int sv;
+
+ if (!g_str_has_prefix(line, SW_VOLUME_STATE))
+ return false;
+
+ line += sizeof(SW_VOLUME_STATE) - 1;
+ sv = strtol(line, &end, 10);
+ if (*end == 0 && sv >= 0 && sv <= 100)
+ software_volume_change(sv);
+ else
+ g_warning("Can't parse software volume: %s\n", line);
+ return true;
+}
+
+void save_sw_volume_state(FILE *fp)
+{
+ fprintf(fp, SW_VOLUME_STATE "%u\n", volume_software_set);
+}
+
+unsigned
+sw_volume_state_get_hash(void)
+{
+ return volume_software_set;
+}
diff --git a/src/Volume.hxx b/src/Volume.hxx
new file mode 100644
index 000000000..024b2840a
--- /dev/null
+++ b/src/Volume.hxx
@@ -0,0 +1,47 @@
+/*
+ * 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_VOLUME_HXX
+#define MPD_VOLUME_HXX
+
+#include <stdio.h>
+
+void volume_init(void);
+
+void volume_finish(void);
+
+int volume_level_get(void);
+
+bool volume_level_change(unsigned volume);
+
+bool
+read_sw_volume_state(const char *line);
+
+void save_sw_volume_state(FILE *fp);
+
+/**
+ * Generates a hash number for the current state of the software
+ * volume control. This is used by timer_save_state_file() to
+ * determine whether the state has changed and the state file should
+ * be saved.
+ */
+unsigned
+sw_volume_state_get_hash(void);
+
+#endif
diff --git a/src/Win32Main.cxx b/src/Win32Main.cxx
new file mode 100644
index 000000000..0cd5e445b
--- /dev/null
+++ b/src/Win32Main.cxx
@@ -0,0 +1,171 @@
+/*
+ * 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.
+ */
+
+#include "config.h"
+#include "Main.hxx"
+
+#ifdef WIN32
+
+#include "mpd_error.h"
+#include "GlobalEvents.hxx"
+
+#include <cstdlib>
+#include <atomic>
+
+#include <glib.h>
+
+#include <windows.h>
+
+static int service_argc;
+static char **service_argv;
+static char service_name[] = "";
+static std::atomic_bool running;
+static SERVICE_STATUS_HANDLE service_handle;
+
+static void WINAPI
+service_main(DWORD argc, CHAR *argv[]);
+
+static SERVICE_TABLE_ENTRY service_registry[] = {
+ {service_name, service_main},
+ {NULL, NULL}
+};
+
+static void
+service_notify_status(DWORD status_code)
+{
+ SERVICE_STATUS current_status;
+
+ current_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
+ current_status.dwControlsAccepted = status_code == SERVICE_START_PENDING
+ ? 0
+ : SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP;
+
+ current_status.dwCurrentState = status_code;
+ current_status.dwWin32ExitCode = NO_ERROR;
+ current_status.dwCheckPoint = 0;
+ current_status.dwWaitHint = 1000;
+
+ SetServiceStatus(service_handle, &current_status);
+}
+
+static DWORD WINAPI
+service_dispatcher(G_GNUC_UNUSED DWORD control, G_GNUC_UNUSED DWORD event_type,
+ G_GNUC_UNUSED void *event_data, G_GNUC_UNUSED void *context)
+{
+ switch (control) {
+ case SERVICE_CONTROL_SHUTDOWN:
+ case SERVICE_CONTROL_STOP:
+ GlobalEvents::Emit(GlobalEvents::SHUTDOWN);
+ return NO_ERROR;
+ default:
+ return NO_ERROR;
+ }
+}
+
+static void WINAPI
+service_main(G_GNUC_UNUSED DWORD argc, G_GNUC_UNUSED CHAR *argv[])
+{
+ DWORD error_code;
+ gchar* error_message;
+
+ service_handle =
+ RegisterServiceCtrlHandlerEx(service_name,
+ service_dispatcher, NULL);
+
+ if (service_handle == 0) {
+ error_code = GetLastError();
+ error_message = g_win32_error_message(error_code);
+ MPD_ERROR("RegisterServiceCtrlHandlerEx() failed: %s",
+ error_message);
+ }
+
+ service_notify_status(SERVICE_START_PENDING);
+ mpd_main(service_argc, service_argv);
+ service_notify_status(SERVICE_STOPPED);
+}
+
+static BOOL WINAPI
+console_handler(DWORD event)
+{
+ switch (event) {
+ case CTRL_C_EVENT:
+ case CTRL_CLOSE_EVENT:
+ if (running.load()) {
+ // Recent msdn docs that process is terminated
+ // if this function returns TRUE.
+ // We initiate correct shutdown sequence (if possible).
+ // Once main() returns CRT will terminate our process
+ // regardless our thread is still active.
+ // If this did not happen within 3 seconds
+ // let's shutdown anyway.
+ GlobalEvents::Emit(GlobalEvents::SHUTDOWN);
+ // Under debugger it's better to wait indefinitely
+ // to allow debugging of shutdown code.
+ Sleep(IsDebuggerPresent() ? INFINITE : 3000);
+ }
+ // If we're not running main loop there is no chance for
+ // clean shutdown.
+ std::exit(EXIT_FAILURE);
+ return TRUE;
+ default:
+ return FALSE;
+ }
+}
+
+int win32_main(int argc, char *argv[])
+{
+ DWORD error_code;
+ gchar* error_message;
+
+ service_argc = argc;
+ service_argv = argv;
+
+ if (StartServiceCtrlDispatcher(service_registry))
+ return 0; /* run as service successefully */
+
+ error_code = GetLastError();
+ if (error_code == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) {
+ /* running as console app */
+ running.store(false);
+ SetConsoleTitle("Music Player Daemon");
+ SetConsoleCtrlHandler(console_handler, TRUE);
+ return mpd_main(argc, argv);
+ }
+
+ error_message = g_win32_error_message(error_code);
+ MPD_ERROR("StartServiceCtrlDispatcher() failed: %s", error_message);
+}
+
+void win32_app_started()
+{
+ if (service_handle != 0)
+ service_notify_status(SERVICE_RUNNING);
+ else
+ running.store(true);
+}
+
+void win32_app_stopping()
+{
+ if (service_handle != 0)
+ service_notify_status(SERVICE_STOP_PENDING);
+ else
+ running.store(false);
+}
+
+#endif
diff --git a/src/ZeroconfAvahi.cxx b/src/ZeroconfAvahi.cxx
new file mode 100644
index 000000000..4764ad755
--- /dev/null
+++ b/src/ZeroconfAvahi.cxx
@@ -0,0 +1,268 @@
+/*
+ * 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 "ZeroconfAvahi.hxx"
+#include "ZeroconfInternal.hxx"
+#include "Listen.hxx"
+#include "event/Loop.hxx"
+#include "mpd_error.h"
+
+#include <glib.h>
+
+#include <avahi-client/client.h>
+#include <avahi-client/publish.h>
+
+#include <avahi-common/alternative.h>
+#include <avahi-common/domain.h>
+#include <avahi-common/malloc.h>
+#include <avahi-common/error.h>
+
+#include <avahi-glib/glib-watch.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "avahi"
+
+static char *avahiName;
+static int avahiRunning;
+static AvahiGLibPoll *avahi_glib_poll;
+static const AvahiPoll *avahi_poll;
+static AvahiClient *avahiClient;
+static AvahiEntryGroup *avahiGroup;
+
+static void avahiRegisterService(AvahiClient * c);
+
+/* Callback when the EntryGroup changes state */
+static void avahiGroupCallback(AvahiEntryGroup * g,
+ AvahiEntryGroupState state,
+ G_GNUC_UNUSED void *userdata)
+{
+ char *n;
+ assert(g);
+
+ g_debug("Service group changed to state %d", state);
+
+ switch (state) {
+ case AVAHI_ENTRY_GROUP_ESTABLISHED:
+ /* The entry group has been established successfully */
+ g_message("Service '%s' successfully established.",
+ avahiName);
+ break;
+
+ case AVAHI_ENTRY_GROUP_COLLISION:
+ /* A service name collision happened. Let's pick a new name */
+ n = avahi_alternative_service_name(avahiName);
+ avahi_free(avahiName);
+ avahiName = n;
+
+ g_message("Service name collision, renaming service to '%s'",
+ avahiName);
+
+ /* And recreate the services */
+ avahiRegisterService(avahi_entry_group_get_client(g));
+ break;
+
+ case AVAHI_ENTRY_GROUP_FAILURE:
+ g_warning("Entry group failure: %s",
+ avahi_strerror(avahi_client_errno
+ (avahi_entry_group_get_client(g))));
+ /* Some kind of failure happened while we were registering our services */
+ avahiRunning = 0;
+ break;
+
+ case AVAHI_ENTRY_GROUP_UNCOMMITED:
+ g_debug("Service group is UNCOMMITED");
+ break;
+ case AVAHI_ENTRY_GROUP_REGISTERING:
+ g_debug("Service group is REGISTERING");
+ }
+}
+
+/* Registers a new service with avahi */
+static void avahiRegisterService(AvahiClient * c)
+{
+ int ret;
+ assert(c);
+ g_debug("Registering service %s/%s", SERVICE_TYPE, avahiName);
+
+ /* If this is the first time we're called,
+ * let's create a new entry group */
+ if (!avahiGroup) {
+ avahiGroup = avahi_entry_group_new(c, avahiGroupCallback, NULL);
+ if (!avahiGroup) {
+ g_warning("Failed to create avahi EntryGroup: %s",
+ avahi_strerror(avahi_client_errno(c)));
+ goto fail;
+ }
+ }
+
+ /* Add the service */
+ /* TODO: This currently binds to ALL interfaces.
+ * We could maybe add a service per actual bound interface,
+ * if that's better. */
+ ret = avahi_entry_group_add_service(avahiGroup,
+ AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
+ AvahiPublishFlags(0),
+ avahiName, SERVICE_TYPE, NULL,
+ NULL, listen_port, NULL);
+ if (ret < 0) {
+ g_warning("Failed to add service %s: %s", SERVICE_TYPE,
+ avahi_strerror(ret));
+ goto fail;
+ }
+
+ /* Tell the server to register the service group */
+ ret = avahi_entry_group_commit(avahiGroup);
+ if (ret < 0) {
+ g_warning("Failed to commit service group: %s",
+ avahi_strerror(ret));
+ goto fail;
+ }
+ return;
+
+fail:
+ avahiRunning = 0;
+}
+
+/* Callback when avahi changes state */
+static void avahiClientCallback(AvahiClient * c, AvahiClientState state,
+ G_GNUC_UNUSED void *userdata)
+{
+ int reason;
+ assert(c);
+
+ /* Called whenever the client or server state changes */
+ g_debug("Client changed to state %d", state);
+
+ switch (state) {
+ case AVAHI_CLIENT_S_RUNNING:
+ g_debug("Client is RUNNING");
+
+ /* The server has startup successfully and registered its host
+ * name on the network, so it's time to create our services */
+ if (!avahiGroup)
+ avahiRegisterService(c);
+ break;
+
+ case AVAHI_CLIENT_FAILURE:
+ reason = avahi_client_errno(c);
+ if (reason == AVAHI_ERR_DISCONNECTED) {
+ g_message("Client Disconnected, will reconnect shortly");
+ if (avahiGroup) {
+ avahi_entry_group_free(avahiGroup);
+ avahiGroup = NULL;
+ }
+ if (avahiClient)
+ avahi_client_free(avahiClient);
+ avahiClient =
+ avahi_client_new(avahi_poll,
+ AVAHI_CLIENT_NO_FAIL,
+ avahiClientCallback, NULL,
+ &reason);
+ if (!avahiClient) {
+ g_warning("Could not reconnect: %s",
+ avahi_strerror(reason));
+ avahiRunning = 0;
+ }
+ } else {
+ g_warning("Client failure: %s (terminal)",
+ avahi_strerror(reason));
+ avahiRunning = 0;
+ }
+ break;
+
+ case AVAHI_CLIENT_S_COLLISION:
+ g_debug("Client is COLLISION");
+ /* Let's drop our registered services. When the server is back
+ * in AVAHI_SERVER_RUNNING state we will register them
+ * again with the new host name. */
+ if (avahiGroup) {
+ g_debug("Resetting group");
+ avahi_entry_group_reset(avahiGroup);
+ }
+
+ case AVAHI_CLIENT_S_REGISTERING:
+ g_debug("Client is REGISTERING");
+ /* The server records are now being established. This
+ * might be caused by a host name change. We need to wait
+ * for our own records to register until the host name is
+ * properly esatblished. */
+
+ if (avahiGroup) {
+ g_debug("Resetting group");
+ avahi_entry_group_reset(avahiGroup);
+ }
+
+ break;
+
+ case AVAHI_CLIENT_CONNECTING:
+ g_debug("Client is CONNECTING");
+ }
+}
+
+void
+AvahiInit(EventLoop &loop, const char *serviceName)
+{
+ int error;
+ g_debug("Initializing interface");
+
+ if (!avahi_is_valid_service_name(serviceName))
+ MPD_ERROR("Invalid zeroconf_name \"%s\"", serviceName);
+
+ avahiName = avahi_strdup(serviceName);
+
+ avahiRunning = 1;
+
+ avahi_glib_poll = avahi_glib_poll_new(loop.GetContext(),
+ G_PRIORITY_DEFAULT);
+ avahi_poll = avahi_glib_poll_get(avahi_glib_poll);
+
+ avahiClient = avahi_client_new(avahi_poll, AVAHI_CLIENT_NO_FAIL,
+ avahiClientCallback, NULL, &error);
+
+ if (!avahiClient) {
+ g_warning("Failed to create client: %s",
+ avahi_strerror(error));
+ AvahiDeinit();
+ }
+}
+
+void
+AvahiDeinit(void)
+{
+ g_debug("Shutting down interface");
+
+ if (avahiGroup) {
+ avahi_entry_group_free(avahiGroup);
+ avahiGroup = NULL;
+ }
+
+ if (avahiClient) {
+ avahi_client_free(avahiClient);
+ avahiClient = NULL;
+ }
+
+ if (avahi_glib_poll != NULL) {
+ avahi_glib_poll_free(avahi_glib_poll);
+ avahi_glib_poll = NULL;
+ }
+
+ avahi_free(avahiName);
+ avahiName = NULL;
+}
diff --git a/src/ZeroconfAvahi.hxx b/src/ZeroconfAvahi.hxx
new file mode 100644
index 000000000..bb046350a
--- /dev/null
+++ b/src/ZeroconfAvahi.hxx
@@ -0,0 +1,31 @@
+/*
+ * 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_ZEROCONF_AVAHI_HXX
+#define MPD_ZEROCONF_AVAHI_HXX
+
+class EventLoop;
+
+void
+AvahiInit(EventLoop &loop, const char *service_name);
+
+void
+AvahiDeinit();
+
+#endif
diff --git a/src/ZeroconfBonjour.cxx b/src/ZeroconfBonjour.cxx
new file mode 100644
index 000000000..959c90242
--- /dev/null
+++ b/src/ZeroconfBonjour.cxx
@@ -0,0 +1,104 @@
+/*
+ * 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 "ZeroconfBonjour.hxx"
+#include "ZeroconfInternal.hxx"
+#include "Listen.hxx"
+#include "event/SocketMonitor.hxx"
+#include "gcc.h"
+
+#include <glib.h>
+
+#include <dns_sd.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "bonjour"
+
+class BonjourMonitor final : public SocketMonitor {
+ DNSServiceRef service_ref;
+
+public:
+ BonjourMonitor(EventLoop &_loop, DNSServiceRef _service_ref)
+ :SocketMonitor(DNSServiceRefSockFD(_service_ref), _loop),
+ service_ref(_service_ref) {
+ ScheduleRead();
+ }
+
+ ~BonjourMonitor() {
+ Steal();
+ DNSServiceRefDeallocate(service_ref);
+ }
+
+protected:
+ virtual bool OnSocketReady(gcc_unused unsigned flags) override {
+ DNSServiceProcessResult(service_ref);
+ return false;
+ }
+};
+
+static BonjourMonitor *bonjour_monitor;
+
+static void
+dnsRegisterCallback(G_GNUC_UNUSED DNSServiceRef sdRef,
+ G_GNUC_UNUSED DNSServiceFlags flags,
+ DNSServiceErrorType errorCode, const char *name,
+ G_GNUC_UNUSED const char *regtype,
+ G_GNUC_UNUSED const char *domain,
+ G_GNUC_UNUSED void *context)
+{
+ if (errorCode != kDNSServiceErr_NoError) {
+ g_warning("Failed to register zeroconf service.");
+
+ bonjour_monitor->Cancel();
+ } else {
+ g_debug("Registered zeroconf service with name '%s'", name);
+ }
+}
+
+void
+BonjourInit(EventLoop &loop, const char *service_name)
+{
+ DNSServiceRef dnsReference;
+ DNSServiceErrorType error = DNSServiceRegister(&dnsReference,
+ 0, 0, service_name,
+ SERVICE_TYPE, NULL, NULL,
+ g_htons(listen_port), 0,
+ NULL,
+ dnsRegisterCallback,
+ NULL);
+
+ if (error != kDNSServiceErr_NoError) {
+ g_warning("Failed to register zeroconf service.");
+
+ if (dnsReference) {
+ DNSServiceRefDeallocate(dnsReference);
+ dnsReference = NULL;
+ }
+ return;
+ }
+
+ bonjour_monitor = new BonjourMonitor(loop, dnsReference);
+}
+
+void
+BonjourDeinit()
+{
+ delete bonjour_monitor;
+}
diff --git a/src/ZeroconfBonjour.hxx b/src/ZeroconfBonjour.hxx
new file mode 100644
index 000000000..d91fe9a0d
--- /dev/null
+++ b/src/ZeroconfBonjour.hxx
@@ -0,0 +1,31 @@
+/*
+ * 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_ZEROCONF_BONJOUR_HXX
+#define MPD_ZEROCONF_BONJOUR_HXX
+
+class EventLoop;
+
+void
+BonjourInit(EventLoop &loop, const char *service_name);
+
+void
+BonjourDeinit();
+
+#endif
diff --git a/src/ZeroconfGlue.cxx b/src/ZeroconfGlue.cxx
new file mode 100644
index 000000000..14e1d0866
--- /dev/null
+++ b/src/ZeroconfGlue.cxx
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+
+#include "config.h"
+#include "ZeroconfGlue.hxx"
+#include "ZeroconfAvahi.hxx"
+#include "ZeroconfBonjour.hxx"
+#include "conf.h"
+#include "Listen.hxx"
+#include "gcc.h"
+
+#include <glib.h>
+
+/* The default service name to publish
+ * (overridden by 'zeroconf_name' config parameter)
+ */
+#define SERVICE_NAME "Music Player"
+
+#define DEFAULT_ZEROCONF_ENABLED 1
+
+static int zeroconfEnabled;
+
+void
+ZeroconfInit(gcc_unused EventLoop &loop)
+{
+ const char *serviceName;
+
+ zeroconfEnabled = config_get_bool(CONF_ZEROCONF_ENABLED,
+ DEFAULT_ZEROCONF_ENABLED);
+ if (!zeroconfEnabled)
+ return;
+
+ if (listen_port <= 0) {
+ g_warning("No global port, disabling zeroconf");
+ zeroconfEnabled = false;
+ return;
+ }
+
+ serviceName = config_get_string(CONF_ZEROCONF_NAME, SERVICE_NAME);
+
+#ifdef HAVE_AVAHI
+ AvahiInit(loop, serviceName);
+#endif
+
+#ifdef HAVE_BONJOUR
+ BonjourInit(loop, serviceName);
+#endif
+}
+
+void
+ZeroconfDeinit()
+{
+ if (!zeroconfEnabled)
+ return;
+
+#ifdef HAVE_AVAHI
+ AvahiDeinit();
+#endif /* HAVE_AVAHI */
+
+#ifdef HAVE_BONJOUR
+ BonjourDeinit();
+#endif
+}
diff --git a/src/ZeroconfGlue.hxx b/src/ZeroconfGlue.hxx
new file mode 100644
index 000000000..2a291ce29
--- /dev/null
+++ b/src/ZeroconfGlue.hxx
@@ -0,0 +1,47 @@
+/*
+ * 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_ZEROCONF_GLUE_HXX
+#define MPD_ZEROCONF_GLUE_HXX
+
+#include "check.h"
+
+class EventLoop;
+
+#ifdef HAVE_ZEROCONF
+
+void
+ZeroconfInit(EventLoop &loop);
+
+void
+ZeroconfDeinit();
+
+#else /* ! HAVE_ZEROCONF */
+
+static inline void
+ZeroconfInit(EventLoop &)
+{}
+
+static inline void
+ZeroconfDeinit()
+{}
+
+#endif /* ! HAVE_ZEROCONF */
+
+#endif
diff --git a/src/ZeroconfInternal.hxx b/src/ZeroconfInternal.hxx
new file mode 100644
index 000000000..2eadcff6e
--- /dev/null
+++ b/src/ZeroconfInternal.hxx
@@ -0,0 +1,26 @@
+/*
+ * 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 ZEROCONF_INTERNAL_H
+#define ZEROCONF_INTERNAL_H
+
+/* The dns-sd service type qualifier to publish */
+#define SERVICE_TYPE "_mpd._tcp"
+
+#endif
diff --git a/src/ape.c b/src/ape.c
deleted file mode 100644
index 6257fe6b3..000000000
--- a/src/ape.c
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "ape.h"
-
-#include <glib.h>
-
-#include <stdint.h>
-#include <assert.h>
-#include <stdio.h>
-#include <string.h>
-
-struct ape_footer {
- unsigned char id[8];
- uint32_t version;
- uint32_t length;
- uint32_t count;
- unsigned char flags[4];
- unsigned char reserved[8];
-};
-
-static bool
-ape_scan_internal(FILE *fp, tag_ape_callback_t callback, void *ctx)
-{
- /* determine if file has an apeV2 tag */
- struct ape_footer footer;
- if (fseek(fp, -(long)sizeof(footer), SEEK_END) ||
- fread(&footer, 1, sizeof(footer), fp) != sizeof(footer) ||
- memcmp(footer.id, "APETAGEX", sizeof(footer.id)) != 0 ||
- GUINT32_FROM_LE(footer.version) != 2000)
- return false;
-
- /* find beginning of ape tag */
- size_t remaining = GUINT32_FROM_LE(footer.length);
- if (remaining <= sizeof(footer) + 10 ||
- /* refuse to load more than one megabyte of tag data */
- remaining > 1024 * 1024 ||
- fseek(fp, -(long)remaining, SEEK_END))
- return false;
-
- /* read tag into buffer */
- remaining -= sizeof(footer);
- assert(remaining > 10);
-
- char *buffer = g_malloc(remaining);
- if (fread(buffer, 1, remaining, fp) != remaining) {
- g_free(buffer);
- return false;
- }
-
- /* read tags */
- unsigned n = GUINT32_FROM_LE(footer.count);
- const char *p = buffer;
- while (n-- && remaining > 10) {
- size_t size = GUINT32_FROM_LE(*(const uint32_t *)p);
- p += 4;
- remaining -= 4;
- unsigned long flags = GUINT32_FROM_LE(*(const uint32_t *)p);
- p += 4;
- remaining -= 4;
-
- /* get the key */
- const char *key = p;
- while (remaining > size && *p != '\0') {
- p++;
- remaining--;
- }
- p++;
- remaining--;
-
- /* get the value */
- if (remaining < size)
- break;
-
- if (!callback(flags, key, p, size, ctx))
- break;
-
- p += size;
- remaining -= size;
- }
-
- g_free(buffer);
- return true;
-}
-
-bool
-tag_ape_scan(const char *path_fs, tag_ape_callback_t callback, void *ctx)
-{
- FILE *fp;
-
- fp = fopen(path_fs, "rb");
- if (fp == NULL)
- return false;
-
- bool success = ape_scan_internal(fp, callback, ctx);
- fclose(fp);
- return success;
-}
diff --git a/src/ape.h b/src/ape.h
deleted file mode 100644
index c2b271b15..000000000
--- a/src/ape.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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_APE_H
-#define MPD_APE_H
-
-#include "check.h"
-
-#include <stdbool.h>
-#include <stddef.h>
-
-typedef bool (*tag_ape_callback_t)(unsigned long flags, const char *key,
- const char *value, size_t value_length,
- void *ctx);
-
-/**
- * Scans the APE tag values from a file.
- *
- * @param path_fs the path of the file in filesystem encoding
- * @return false if the file could not be opened or if no APE tag is
- * present
- */
-bool
-tag_ape_scan(const char *path_fs, tag_ape_callback_t callback, void *ctx);
-
-#endif
diff --git a/src/archive/Bzip2ArchivePlugin.cxx b/src/archive/Bzip2ArchivePlugin.cxx
new file mode 100644
index 000000000..182b9ccd1
--- /dev/null
+++ b/src/archive/Bzip2ArchivePlugin.cxx
@@ -0,0 +1,298 @@
+/*
+ * 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.
+ */
+
+/**
+ * single bz2 archive handling (requires libbz2)
+ */
+
+#include "config.h"
+#include "Bzip2ArchivePlugin.hxx"
+#include "ArchivePlugin.hxx"
+#include "ArchiveFile.hxx"
+#include "ArchiveVisitor.hxx"
+#include "InputInternal.hxx"
+#include "InputStream.hxx"
+#include "InputPlugin.hxx"
+#include "util/RefCount.hxx"
+
+#include <stdint.h>
+#include <stddef.h>
+#include <string.h>
+#include <glib.h>
+#include <bzlib.h>
+
+#ifdef HAVE_OLDER_BZIP2
+#define BZ2_bzDecompressInit bzDecompressInit
+#define BZ2_bzDecompress bzDecompress
+#endif
+
+class Bzip2ArchiveFile final : public ArchiveFile {
+public:
+ RefCount ref;
+
+ char *const name;
+ struct input_stream *const istream;
+
+ Bzip2ArchiveFile(const char *path, input_stream *_is)
+ :ArchiveFile(bz2_archive_plugin),
+ name(g_path_get_basename(path)),
+ istream(_is) {
+ // remove .bz2 suffix
+ size_t len = strlen(name);
+ if (len > 4)
+ name[len - 4] = 0;
+ }
+
+ ~Bzip2ArchiveFile() {
+ input_stream_close(istream);
+ }
+
+ void Ref() {
+ ref.Increment();
+ }
+
+ void Unref() {
+ if (!ref.Decrement())
+ return;
+
+ g_free(name);
+ delete this;
+ }
+
+ virtual void Close() override {
+ Unref();
+ }
+
+ virtual void Visit(ArchiveVisitor &visitor) override {
+ visitor.VisitArchiveEntry(name);
+ }
+
+ virtual input_stream *OpenStream(const char *path,
+ Mutex &mutex, Cond &cond,
+ GError **error_r) override;
+};
+
+struct Bzip2InputStream {
+ struct input_stream base;
+
+ Bzip2ArchiveFile *archive;
+
+ bool eof;
+
+ bz_stream bzstream;
+
+ char buffer[5000];
+
+ Bzip2InputStream(Bzip2ArchiveFile &context, const char *uri,
+ Mutex &mutex, Cond &cond);
+ ~Bzip2InputStream();
+
+ bool Open(GError **error_r);
+ void Close();
+};
+
+extern const struct input_plugin bz2_inputplugin;
+
+static inline GQuark
+bz2_quark(void)
+{
+ return g_quark_from_static_string("bz2");
+}
+
+/* single archive handling allocation helpers */
+
+inline bool
+Bzip2InputStream::Open(GError **error_r)
+{
+ bzstream.bzalloc = nullptr;
+ bzstream.bzfree = nullptr;
+ bzstream.opaque = nullptr;
+
+ bzstream.next_in = (char *)buffer;
+ bzstream.avail_in = 0;
+
+ int ret = BZ2_bzDecompressInit(&bzstream, 0, 0);
+ if (ret != BZ_OK) {
+ g_set_error(error_r, bz2_quark(), ret,
+ "BZ2_bzDecompressInit() has failed");
+ return false;
+ }
+
+ base.ready = true;
+ return true;
+}
+
+inline void
+Bzip2InputStream::Close()
+{
+ BZ2_bzDecompressEnd(&bzstream);
+}
+
+/* archive open && listing routine */
+
+static ArchiveFile *
+bz2_open(const char *pathname, GError **error_r)
+{
+ static Mutex mutex;
+ static Cond cond;
+ input_stream *is = input_stream_open(pathname, mutex, cond, error_r);
+ if (is == nullptr)
+ return nullptr;
+
+ return new Bzip2ArchiveFile(pathname, is);
+}
+
+/* single archive handling */
+
+Bzip2InputStream::Bzip2InputStream(Bzip2ArchiveFile &_context, const char *uri,
+ Mutex &mutex, Cond &cond)
+ :base(bz2_inputplugin, uri, mutex, cond),
+ archive(&_context), eof(false)
+{
+ archive->Ref();
+}
+
+Bzip2InputStream::~Bzip2InputStream()
+{
+ archive->Unref();
+}
+
+input_stream *
+Bzip2ArchiveFile::OpenStream(const char *path,
+ Mutex &mutex, Cond &cond,
+ GError **error_r)
+{
+ Bzip2InputStream *bis = new Bzip2InputStream(*this, path, mutex, cond);
+ if (!bis->Open(error_r)) {
+ delete bis;
+ return NULL;
+ }
+
+ return &bis->base;
+}
+
+static void
+bz2_is_close(struct input_stream *is)
+{
+ Bzip2InputStream *bis = (Bzip2InputStream *)is;
+
+ bis->Close();
+ delete bis;
+}
+
+static bool
+bz2_fillbuffer(Bzip2InputStream *bis, GError **error_r)
+{
+ size_t count;
+ bz_stream *bzstream;
+
+ bzstream = &bis->bzstream;
+
+ if (bzstream->avail_in > 0)
+ return true;
+
+ count = input_stream_read(bis->archive->istream,
+ bis->buffer, sizeof(bis->buffer),
+ error_r);
+ if (count == 0)
+ return false;
+
+ bzstream->next_in = bis->buffer;
+ bzstream->avail_in = count;
+ return true;
+}
+
+static size_t
+bz2_is_read(struct input_stream *is, void *ptr, size_t length,
+ GError **error_r)
+{
+ Bzip2InputStream *bis = (Bzip2InputStream *)is;
+ bz_stream *bzstream;
+ int bz_result;
+ size_t nbytes = 0;
+
+ if (bis->eof)
+ return 0;
+
+ bzstream = &bis->bzstream;
+ bzstream->next_out = (char *)ptr;
+ bzstream->avail_out = length;
+
+ do {
+ if (!bz2_fillbuffer(bis, error_r))
+ return 0;
+
+ bz_result = BZ2_bzDecompress(bzstream);
+
+ if (bz_result == BZ_STREAM_END) {
+ bis->eof = true;
+ break;
+ }
+
+ if (bz_result != BZ_OK) {
+ g_set_error(error_r, bz2_quark(), bz_result,
+ "BZ2_bzDecompress() has failed");
+ return 0;
+ }
+ } while (bzstream->avail_out == length);
+
+ nbytes = length - bzstream->avail_out;
+ is->offset += nbytes;
+
+ return nbytes;
+}
+
+static bool
+bz2_is_eof(struct input_stream *is)
+{
+ Bzip2InputStream *bis = (Bzip2InputStream *)is;
+
+ return bis->eof;
+}
+
+/* exported structures */
+
+static const char *const bz2_extensions[] = {
+ "bz2",
+ NULL
+};
+
+const struct input_plugin bz2_inputplugin = {
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ bz2_is_close,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ bz2_is_read,
+ bz2_is_eof,
+ nullptr,
+};
+
+const struct archive_plugin bz2_archive_plugin = {
+ "bz2",
+ nullptr,
+ nullptr,
+ bz2_open,
+ bz2_extensions,
+};
+
diff --git a/src/archive/Bzip2ArchivePlugin.hxx b/src/archive/Bzip2ArchivePlugin.hxx
new file mode 100644
index 000000000..a7933a7a7
--- /dev/null
+++ b/src/archive/Bzip2ArchivePlugin.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_ARCHIVE_BZ2_HXX
+#define MPD_ARCHIVE_BZ2_HXX
+
+extern const struct archive_plugin bz2_archive_plugin;
+
+#endif
diff --git a/src/archive/Iso9660ArchivePlugin.cxx b/src/archive/Iso9660ArchivePlugin.cxx
new file mode 100644
index 000000000..97fd8bd52
--- /dev/null
+++ b/src/archive/Iso9660ArchivePlugin.cxx
@@ -0,0 +1,266 @@
+/*
+ * 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.
+ */
+
+/**
+ * iso archive handling (requires cdio, and iso9660)
+ */
+
+#include "config.h"
+#include "Iso9660ArchivePlugin.hxx"
+#include "ArchivePlugin.hxx"
+#include "ArchiveFile.hxx"
+#include "ArchiveVisitor.hxx"
+#include "InputInternal.hxx"
+#include "InputStream.hxx"
+#include "InputPlugin.hxx"
+#include "util/RefCount.hxx"
+
+#include <cdio/cdio.h>
+#include <cdio/iso9660.h>
+
+#include <glib.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#define CEILING(x, y) ((x+(y-1))/y)
+
+class Iso9660ArchiveFile final : public ArchiveFile {
+public:
+ RefCount ref;
+
+ iso9660_t *iso;
+
+ Iso9660ArchiveFile(iso9660_t *_iso)
+ :ArchiveFile(iso9660_archive_plugin), iso(_iso) {}
+
+ ~Iso9660ArchiveFile() {
+ iso9660_close(iso);
+ }
+
+ void Unref() {
+ if (ref.Decrement())
+ delete this;
+ }
+
+ void Visit(const char *path, ArchiveVisitor &visitor);
+
+ virtual void Close() override {
+ Unref();
+ }
+
+ virtual void Visit(ArchiveVisitor &visitor) override;
+
+ virtual input_stream *OpenStream(const char *path,
+ Mutex &mutex, Cond &cond,
+ GError **error_r) override;
+};
+
+extern const struct input_plugin iso9660_input_plugin;
+
+static inline GQuark
+iso9660_quark(void)
+{
+ return g_quark_from_static_string("iso9660");
+}
+
+/* archive open && listing routine */
+
+inline void
+Iso9660ArchiveFile::Visit(const char *psz_path, ArchiveVisitor &visitor)
+{
+ CdioList_t *entlist;
+ CdioListNode_t *entnode;
+ iso9660_stat_t *statbuf;
+ char pathname[4096];
+
+ entlist = iso9660_ifs_readdir (iso, psz_path);
+ if (!entlist) {
+ return;
+ }
+ /* Iterate over the list of nodes that iso9660_ifs_readdir gives */
+ _CDIO_LIST_FOREACH (entnode, entlist) {
+ statbuf = (iso9660_stat_t *) _cdio_list_node_data (entnode);
+
+ strcpy(pathname, psz_path);
+ strcat(pathname, statbuf->filename);
+
+ if (iso9660_stat_s::_STAT_DIR == statbuf->type ) {
+ if (strcmp(statbuf->filename, ".") && strcmp(statbuf->filename, "..")) {
+ strcat(pathname, "/");
+ Visit(pathname, visitor);
+ }
+ } else {
+ //remove leading /
+ visitor.VisitArchiveEntry(pathname + 1);
+ }
+ }
+ _cdio_list_free (entlist, true);
+}
+
+static ArchiveFile *
+iso9660_archive_open(const char *pathname, GError **error_r)
+{
+ /* open archive */
+ auto iso = iso9660_open(pathname);
+ if (iso == nullptr) {
+ g_set_error(error_r, iso9660_quark(), 0,
+ "Failed to open ISO9660 file %s", pathname);
+ return NULL;
+ }
+
+ return new Iso9660ArchiveFile(iso);
+}
+
+void
+Iso9660ArchiveFile::Visit(ArchiveVisitor &visitor)
+{
+ Visit("/", visitor);
+}
+
+/* single archive handling */
+
+struct Iso9660InputStream {
+ struct input_stream base;
+
+ Iso9660ArchiveFile *archive;
+
+ iso9660_stat_t *statbuf;
+ size_t max_blocks;
+
+ 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)) {
+
+ base.ready = true;
+ base.size = statbuf->size;
+
+ archive->ref.Increment();
+ }
+
+ ~Iso9660InputStream() {
+ free(statbuf);
+ archive->Unref();
+ }
+};
+
+input_stream *
+Iso9660ArchiveFile::OpenStream(const char *pathname,
+ Mutex &mutex, Cond &cond,
+ GError **error_r)
+{
+ auto statbuf = iso9660_ifs_stat_translate(iso, pathname);
+ if (statbuf == nullptr) {
+ g_set_error(error_r, iso9660_quark(), 0,
+ "not found in the ISO file: %s", pathname);
+ return NULL;
+ }
+
+ Iso9660InputStream *iis =
+ new Iso9660InputStream(*this, pathname, mutex, cond,
+ statbuf);
+ return &iis->base;
+}
+
+static void
+iso9660_input_close(struct input_stream *is)
+{
+ Iso9660InputStream *iis = (Iso9660InputStream *)is;
+
+ delete iis;
+}
+
+
+static size_t
+iso9660_input_read(struct input_stream *is, void *ptr, size_t size, GError **error_r)
+{
+ Iso9660InputStream *iis = (Iso9660InputStream *)is;
+ int toread, readed = 0;
+ int no_blocks, cur_block;
+ size_t left_bytes = iis->statbuf->size - is->offset;
+
+ size = (size * ISO_BLOCKSIZE) / ISO_BLOCKSIZE;
+
+ if (left_bytes < size) {
+ toread = left_bytes;
+ no_blocks = CEILING(left_bytes,ISO_BLOCKSIZE);
+ } else {
+ toread = size;
+ no_blocks = toread / ISO_BLOCKSIZE;
+ }
+ if (no_blocks > 0) {
+
+ cur_block = is->offset / ISO_BLOCKSIZE;
+
+ readed = iso9660_iso_seek_read (iis->archive->iso, ptr,
+ iis->statbuf->lsn + cur_block, no_blocks);
+
+ if (readed != no_blocks * ISO_BLOCKSIZE) {
+ g_set_error(error_r, iso9660_quark(), 0,
+ "error reading ISO file at lsn %lu",
+ (long unsigned int) cur_block);
+ return 0;
+ }
+ if (left_bytes < size) {
+ readed = left_bytes;
+ }
+
+ is->offset += readed;
+ }
+ return readed;
+}
+
+static bool
+iso9660_input_eof(struct input_stream *is)
+{
+ return is->offset == is->size;
+}
+
+/* exported structures */
+
+static const char *const iso9660_archive_extensions[] = {
+ "iso",
+ NULL
+};
+
+const struct input_plugin iso9660_input_plugin = {
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ iso9660_input_close,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ iso9660_input_read,
+ iso9660_input_eof,
+ nullptr,
+};
+
+const struct archive_plugin iso9660_archive_plugin = {
+ "iso",
+ nullptr,
+ nullptr,
+ iso9660_archive_open,
+ iso9660_archive_extensions,
+};
diff --git a/src/archive/Iso9660ArchivePlugin.hxx b/src/archive/Iso9660ArchivePlugin.hxx
new file mode 100644
index 000000000..6fbab6159
--- /dev/null
+++ b/src/archive/Iso9660ArchivePlugin.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_ARCHIVE_ISO9660_HXX
+#define MPD_ARCHIVE_ISO9660_HXX
+
+extern const struct archive_plugin iso9660_archive_plugin;
+
+#endif
diff --git a/src/archive/ZzipArchivePlugin.cxx b/src/archive/ZzipArchivePlugin.cxx
new file mode 100644
index 000000000..d0db7aa37
--- /dev/null
+++ b/src/archive/ZzipArchivePlugin.cxx
@@ -0,0 +1,230 @@
+/*
+ * 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.
+ */
+
+/**
+ * zip archive handling (requires zziplib)
+ */
+
+#include "config.h"
+#include "ZzipArchivePlugin.hxx"
+#include "ArchivePlugin.hxx"
+#include "ArchiveFile.hxx"
+#include "ArchiveVisitor.hxx"
+#include "InputInternal.hxx"
+#include "InputStream.hxx"
+#include "InputPlugin.hxx"
+#include "util/RefCount.hxx"
+
+#include <zzip/zzip.h>
+#include <glib.h>
+#include <string.h>
+
+class ZzipArchiveFile final : public ArchiveFile {
+public:
+ RefCount ref;
+
+ ZZIP_DIR *const dir;
+
+ ZzipArchiveFile(ZZIP_DIR *_dir)
+ :ArchiveFile(zzip_archive_plugin), dir(_dir) {}
+
+ ~ZzipArchiveFile() {
+ zzip_dir_close(dir);
+ }
+
+ void Unref() {
+ if (ref.Decrement())
+ delete this;
+ }
+
+ virtual void Close() override {
+ Unref();
+ }
+
+ virtual void Visit(ArchiveVisitor &visitor) override;
+
+ virtual input_stream *OpenStream(const char *path,
+ Mutex &mutex, Cond &cond,
+ GError **error_r) override;
+};
+
+extern const struct input_plugin zzip_input_plugin;
+
+static inline GQuark
+zzip_quark(void)
+{
+ return g_quark_from_static_string("zzip");
+}
+
+/* archive open && listing routine */
+
+static ArchiveFile *
+zzip_archive_open(const char *pathname, GError **error_r)
+{
+ ZZIP_DIR *dir = zzip_dir_open(pathname, NULL);
+ if (dir == nullptr) {
+ g_set_error(error_r, zzip_quark(), 0,
+ "Failed to open ZIP file %s", pathname);
+ return NULL;
+ }
+
+ return new ZzipArchiveFile(dir);
+}
+
+inline void
+ZzipArchiveFile::Visit(ArchiveVisitor &visitor)
+{
+ zzip_rewinddir(dir);
+
+ ZZIP_DIRENT dirent;
+ while (zzip_dir_read(dir, &dirent))
+ //add only files
+ if (dirent.st_size > 0)
+ visitor.VisitArchiveEntry(dirent.d_name);
+}
+
+/* single archive handling */
+
+struct ZzipInputStream {
+ struct input_stream base;
+
+ ZzipArchiveFile *archive;
+
+ ZZIP_FILE *file;
+
+ ZzipInputStream(ZzipArchiveFile &_archive, const char *uri,
+ Mutex &mutex, Cond &cond,
+ ZZIP_FILE *_file)
+ :base(zzip_input_plugin, uri, mutex, cond),
+ archive(&_archive), file(_file) {
+ base.ready = true;
+ //we are seekable (but its not recommendent to do so)
+ base.seekable = true;
+
+ ZZIP_STAT z_stat;
+ zzip_file_stat(file, &z_stat);
+ base.size = z_stat.st_size;
+
+ archive->ref.Increment();
+ }
+
+ ~ZzipInputStream() {
+ zzip_file_close(file);
+ archive->Unref();
+ }
+};
+
+input_stream *
+ZzipArchiveFile::OpenStream(const char *pathname,
+ Mutex &mutex, Cond &cond,
+ GError **error_r)
+{
+ ZZIP_FILE *_file = zzip_file_open(dir, pathname, 0);
+ if (_file == nullptr) {
+ g_set_error(error_r, zzip_quark(), 0,
+ "not found in the ZIP file: %s", pathname);
+ return NULL;
+ }
+
+ ZzipInputStream *zis =
+ new ZzipInputStream(*this, pathname,
+ mutex, cond,
+ _file);
+ return &zis->base;
+}
+
+static void
+zzip_input_close(struct input_stream *is)
+{
+ ZzipInputStream *zis = (ZzipInputStream *)is;
+
+ delete zis;
+}
+
+static size_t
+zzip_input_read(struct input_stream *is, void *ptr, size_t size,
+ GError **error_r)
+{
+ ZzipInputStream *zis = (ZzipInputStream *)is;
+ int ret;
+
+ ret = zzip_file_read(zis->file, ptr, size);
+ if (ret < 0) {
+ g_set_error(error_r, zzip_quark(), ret,
+ "zzip_file_read() has failed");
+ return 0;
+ }
+
+ is->offset = zzip_tell(zis->file);
+
+ return ret;
+}
+
+static bool
+zzip_input_eof(struct input_stream *is)
+{
+ ZzipInputStream *zis = (ZzipInputStream *)is;
+
+ return (goffset)zzip_tell(zis->file) == is->size;
+}
+
+static bool
+zzip_input_seek(struct input_stream *is,
+ goffset offset, int whence, GError **error_r)
+{
+ ZzipInputStream *zis = (ZzipInputStream *)is;
+ zzip_off_t ofs = zzip_seek(zis->file, offset, whence);
+ if (ofs != -1) {
+ g_set_error(error_r, zzip_quark(), ofs,
+ "zzip_seek() has failed");
+ is->offset = ofs;
+ return true;
+ }
+ return false;
+}
+
+/* exported structures */
+
+static const char *const zzip_archive_extensions[] = {
+ "zip",
+ NULL
+};
+
+const struct input_plugin zzip_input_plugin = {
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ zzip_input_close,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ zzip_input_read,
+ zzip_input_eof,
+ zzip_input_seek,
+};
+
+const struct archive_plugin zzip_archive_plugin = {
+ "zzip",
+ nullptr,
+ nullptr,
+ zzip_archive_open,
+ zzip_archive_extensions,
+};
diff --git a/src/archive/ZzipArchivePlugin.hxx b/src/archive/ZzipArchivePlugin.hxx
new file mode 100644
index 000000000..4ba16849b
--- /dev/null
+++ b/src/archive/ZzipArchivePlugin.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_ARCHIVE_ZZIP_HXX
+#define MPD_ARCHIVE_ZZIP_HXX
+
+extern const struct archive_plugin zzip_archive_plugin;
+
+#endif
diff --git a/src/archive/bz2_archive_plugin.c b/src/archive/bz2_archive_plugin.c
deleted file mode 100644
index e2420048b..000000000
--- a/src/archive/bz2_archive_plugin.c
+++ /dev/null
@@ -1,314 +0,0 @@
-/*
- * 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.
- */
-
-/**
- * single bz2 archive handling (requires libbz2)
- */
-
-#include "config.h"
-#include "archive/bz2_archive_plugin.h"
-#include "archive_api.h"
-#include "input_internal.h"
-#include "input_plugin.h"
-#include "refcount.h"
-
-#include <stdint.h>
-#include <stddef.h>
-#include <string.h>
-#include <glib.h>
-#include <bzlib.h>
-
-#ifdef HAVE_OLDER_BZIP2
-#define BZ2_bzDecompressInit bzDecompressInit
-#define BZ2_bzDecompress bzDecompress
-#endif
-
-struct bz2_archive_file {
- struct archive_file base;
-
- struct refcount ref;
-
- char *name;
- bool reset;
- struct input_stream *istream;
-};
-
-struct bz2_input_stream {
- struct input_stream base;
-
- struct bz2_archive_file *archive;
-
- bool eof;
-
- bz_stream bzstream;
-
- char buffer[5000];
-};
-
-static const struct input_plugin bz2_inputplugin;
-
-static inline GQuark
-bz2_quark(void)
-{
- return g_quark_from_static_string("bz2");
-}
-
-/* single archive handling allocation helpers */
-
-static bool
-bz2_alloc(struct bz2_input_stream *data, GError **error_r)
-{
- int ret;
-
- data->bzstream.bzalloc = NULL;
- data->bzstream.bzfree = NULL;
- data->bzstream.opaque = NULL;
-
- data->bzstream.next_in = (void *) data->buffer;
- data->bzstream.avail_in = 0;
-
- ret = BZ2_bzDecompressInit(&data->bzstream, 0, 0);
- if (ret != BZ_OK) {
- g_free(data);
-
- g_set_error(error_r, bz2_quark(), ret,
- "BZ2_bzDecompressInit() has failed");
- return false;
- }
-
- return true;
-}
-
-static void
-bz2_destroy(struct bz2_input_stream *data)
-{
- BZ2_bzDecompressEnd(&data->bzstream);
-}
-
-/* archive open && listing routine */
-
-#if GCC_CHECK_VERSION(4, 2)
-/* workaround for a warning caused by G_STATIC_MUTEX_INIT */
-#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
-#endif
-
-static struct archive_file *
-bz2_open(const char *pathname, GError **error_r)
-{
- struct bz2_archive_file *context;
- int len;
-
- context = g_malloc(sizeof(*context));
- archive_file_init(&context->base, &bz2_archive_plugin);
- refcount_init(&context->ref);
-
- //open archive
- static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
- context->istream = input_stream_open(pathname,
- g_static_mutex_get_mutex(&mutex),
- NULL,
- error_r);
- if (context->istream == NULL) {
- g_free(context);
- return NULL;
- }
-
- context->name = g_path_get_basename(pathname);
-
- //remove suffix
- len = strlen(context->name);
- if (len > 4) {
- context->name[len - 4] = 0; //remove .bz2 suffix
- }
-
- return &context->base;
-}
-
-static void
-bz2_scan_reset(struct archive_file *file)
-{
- struct bz2_archive_file *context = (struct bz2_archive_file *) file;
- context->reset = true;
-}
-
-static char *
-bz2_scan_next(struct archive_file *file)
-{
- struct bz2_archive_file *context = (struct bz2_archive_file *) file;
- char *name = NULL;
-
- if (context->reset) {
- name = context->name;
- context->reset = false;
- }
-
- return name;
-}
-
-static void
-bz2_close(struct archive_file *file)
-{
- struct bz2_archive_file *context = (struct bz2_archive_file *) file;
-
- if (!refcount_dec(&context->ref))
- return;
-
- g_free(context->name);
-
- input_stream_close(context->istream);
- g_free(context);
-}
-
-/* single archive handling */
-
-static struct input_stream *
-bz2_open_stream(struct archive_file *file, const char *path,
- GMutex *mutex, GCond *cond,
- GError **error_r)
-{
- struct bz2_archive_file *context = (struct bz2_archive_file *) file;
- struct bz2_input_stream *bis = g_new(struct bz2_input_stream, 1);
-
- input_stream_init(&bis->base, &bz2_inputplugin, path,
- mutex, cond);
-
- bis->archive = context;
-
- bis->base.ready = true;
- bis->base.seekable = false;
-
- if (!bz2_alloc(bis, error_r)) {
- input_stream_deinit(&bis->base);
- g_free(bis);
- return NULL;
- }
-
- bis->eof = false;
-
- refcount_inc(&context->ref);
-
- return &bis->base;
-}
-
-static void
-bz2_is_close(struct input_stream *is)
-{
- struct bz2_input_stream *bis = (struct bz2_input_stream *)is;
-
- bz2_destroy(bis);
-
- bz2_close(&bis->archive->base);
-
- input_stream_deinit(&bis->base);
- g_free(bis);
-}
-
-static bool
-bz2_fillbuffer(struct bz2_input_stream *bis, GError **error_r)
-{
- size_t count;
- bz_stream *bzstream;
-
- bzstream = &bis->bzstream;
-
- if (bzstream->avail_in > 0)
- return true;
-
- count = input_stream_read(bis->archive->istream,
- bis->buffer, sizeof(bis->buffer),
- error_r);
- if (count == 0)
- return false;
-
- bzstream->next_in = bis->buffer;
- bzstream->avail_in = count;
- return true;
-}
-
-static size_t
-bz2_is_read(struct input_stream *is, void *ptr, size_t length,
- GError **error_r)
-{
- struct bz2_input_stream *bis = (struct bz2_input_stream *)is;
- bz_stream *bzstream;
- int bz_result;
- size_t nbytes = 0;
-
- if (bis->eof)
- return 0;
-
- bzstream = &bis->bzstream;
- bzstream->next_out = ptr;
- bzstream->avail_out = length;
-
- do {
- if (!bz2_fillbuffer(bis, error_r))
- return 0;
-
- bz_result = BZ2_bzDecompress(bzstream);
-
- if (bz_result == BZ_STREAM_END) {
- bis->eof = true;
- break;
- }
-
- if (bz_result != BZ_OK) {
- g_set_error(error_r, bz2_quark(), bz_result,
- "BZ2_bzDecompress() has failed");
- return 0;
- }
- } while (bzstream->avail_out == length);
-
- nbytes = length - bzstream->avail_out;
- is->offset += nbytes;
-
- return nbytes;
-}
-
-static bool
-bz2_is_eof(struct input_stream *is)
-{
- struct bz2_input_stream *bis = (struct bz2_input_stream *)is;
-
- return bis->eof;
-}
-
-/* exported structures */
-
-static const char *const bz2_extensions[] = {
- "bz2",
- NULL
-};
-
-static const struct input_plugin bz2_inputplugin = {
- .close = bz2_is_close,
- .read = bz2_is_read,
- .eof = bz2_is_eof,
-};
-
-const struct archive_plugin bz2_archive_plugin = {
- .name = "bz2",
- .open = bz2_open,
- .scan_reset = bz2_scan_reset,
- .scan_next = bz2_scan_next,
- .open_stream = bz2_open_stream,
- .close = bz2_close,
- .suffixes = bz2_extensions
-};
-
diff --git a/src/archive/bz2_archive_plugin.h b/src/archive/bz2_archive_plugin.h
deleted file mode 100644
index 46c69a66c..000000000
--- a/src/archive/bz2_archive_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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_ARCHIVE_BZ2_H
-#define MPD_ARCHIVE_BZ2_H
-
-extern const struct archive_plugin bz2_archive_plugin;
-
-#endif
diff --git a/src/archive/iso9660_archive_plugin.c b/src/archive/iso9660_archive_plugin.c
deleted file mode 100644
index bb6cb9588..000000000
--- a/src/archive/iso9660_archive_plugin.c
+++ /dev/null
@@ -1,290 +0,0 @@
-/*
- * 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.
- */
-
-/**
- * iso archive handling (requires cdio, and iso9660)
- */
-
-#include "config.h"
-#include "archive/iso9660_archive_plugin.h"
-#include "archive_api.h"
-#include "input_internal.h"
-#include "input_plugin.h"
-#include "refcount.h"
-
-#include <cdio/cdio.h>
-#include <cdio/iso9660.h>
-
-#include <glib.h>
-#include <string.h>
-
-#define CEILING(x, y) ((x+(y-1))/y)
-
-struct iso9660_archive_file {
- struct archive_file base;
-
- struct refcount ref;
-
- iso9660_t *iso;
- GSList *list;
- GSList *iter;
-};
-
-static const struct input_plugin iso9660_input_plugin;
-
-static inline GQuark
-iso9660_quark(void)
-{
- return g_quark_from_static_string("iso9660");
-}
-
-/* archive open && listing routine */
-
-static void
-listdir_recur(const char *psz_path, struct iso9660_archive_file *context)
-{
- iso9660_t *iso = context->iso;
- CdioList_t *entlist;
- CdioListNode_t *entnode;
- iso9660_stat_t *statbuf;
- char pathname[4096];
-
- entlist = iso9660_ifs_readdir (iso, psz_path);
- if (!entlist) {
- return;
- }
- /* Iterate over the list of nodes that iso9660_ifs_readdir gives */
- _CDIO_LIST_FOREACH (entnode, entlist) {
- statbuf = (iso9660_stat_t *) _cdio_list_node_data (entnode);
-
- strcpy(pathname, psz_path);
- strcat(pathname, statbuf->filename);
-
- if (_STAT_DIR == statbuf->type ) {
- if (strcmp(statbuf->filename, ".") && strcmp(statbuf->filename, "..")) {
- strcat(pathname, "/");
- listdir_recur(pathname, context);
- }
- } else {
- //remove leading /
- context->list = g_slist_prepend( context->list,
- g_strdup(pathname + 1));
- }
- }
- _cdio_list_free (entlist, true);
-}
-
-static struct archive_file *
-iso9660_archive_open(const char *pathname, GError **error_r)
-{
- struct iso9660_archive_file *context =
- g_new(struct iso9660_archive_file, 1);
-
- archive_file_init(&context->base, &iso9660_archive_plugin);
- refcount_init(&context->ref);
-
- context->list = NULL;
-
- /* open archive */
- context->iso = iso9660_open (pathname);
- if (context->iso == NULL) {
- g_set_error(error_r, iso9660_quark(), 0,
- "Failed to open ISO9660 file %s", pathname);
- return NULL;
- }
-
- listdir_recur("/", context);
-
- return &context->base;
-}
-
-static void
-iso9660_archive_scan_reset(struct archive_file *file)
-{
- struct iso9660_archive_file *context =
- (struct iso9660_archive_file *)file;
-
- //reset iterator
- context->iter = context->list;
-}
-
-static char *
-iso9660_archive_scan_next(struct archive_file *file)
-{
- struct iso9660_archive_file *context =
- (struct iso9660_archive_file *)file;
-
- char *data = NULL;
- if (context->iter != NULL) {
- ///fetch data and goto next
- data = context->iter->data;
- context->iter = g_slist_next(context->iter);
- }
- return data;
-}
-
-static void
-iso9660_archive_close(struct archive_file *file)
-{
- struct iso9660_archive_file *context =
- (struct iso9660_archive_file *)file;
- GSList *tmp;
-
- if (!refcount_dec(&context->ref))
- return;
-
- if (context->list) {
- //free list
- for (tmp = context->list; tmp != NULL; tmp = g_slist_next(tmp))
- g_free(tmp->data);
- g_slist_free(context->list);
- }
- //close archive
- iso9660_close(context->iso);
-
- g_free(context);
-}
-
-/* single archive handling */
-
-struct iso9660_input_stream {
- struct input_stream base;
-
- struct iso9660_archive_file *archive;
-
- iso9660_stat_t *statbuf;
- size_t max_blocks;
-};
-
-static struct input_stream *
-iso9660_archive_open_stream(struct archive_file *file, const char *pathname,
- GMutex *mutex, GCond *cond,
- GError **error_r)
-{
- struct iso9660_archive_file *context =
- (struct iso9660_archive_file *)file;
- struct iso9660_input_stream *iis;
-
- iis = g_new(struct iso9660_input_stream, 1);
- input_stream_init(&iis->base, &iso9660_input_plugin, pathname,
- mutex, cond);
-
- iis->archive = context;
- iis->statbuf = iso9660_ifs_stat_translate(context->iso, pathname);
- if (iis->statbuf == NULL) {
- g_free(iis);
- g_set_error(error_r, iso9660_quark(), 0,
- "not found in the ISO file: %s", pathname);
- return NULL;
- }
-
- iis->base.ready = true;
- //we are not seekable
- iis->base.seekable = false;
-
- iis->base.size = iis->statbuf->size;
-
- iis->max_blocks = CEILING(iis->statbuf->size, ISO_BLOCKSIZE);
-
- refcount_inc(&context->ref);
-
- return &iis->base;
-}
-
-static void
-iso9660_input_close(struct input_stream *is)
-{
- struct iso9660_input_stream *iis = (struct iso9660_input_stream *)is;
-
- g_free(iis->statbuf);
-
- iso9660_archive_close(&iis->archive->base);
-
- input_stream_deinit(&iis->base);
- g_free(iis);
-}
-
-
-static size_t
-iso9660_input_read(struct input_stream *is, void *ptr, size_t size, GError **error_r)
-{
- struct iso9660_input_stream *iis = (struct iso9660_input_stream *)is;
- int toread, readed = 0;
- int no_blocks, cur_block;
- size_t left_bytes = iis->statbuf->size - is->offset;
-
- size = (size * ISO_BLOCKSIZE) / ISO_BLOCKSIZE;
-
- if (left_bytes < size) {
- toread = left_bytes;
- no_blocks = CEILING(left_bytes,ISO_BLOCKSIZE);
- } else {
- toread = size;
- no_blocks = toread / ISO_BLOCKSIZE;
- }
- if (no_blocks > 0) {
-
- cur_block = is->offset / ISO_BLOCKSIZE;
-
- readed = iso9660_iso_seek_read (iis->archive->iso, ptr,
- iis->statbuf->lsn + cur_block, no_blocks);
-
- if (readed != no_blocks * ISO_BLOCKSIZE) {
- g_set_error(error_r, iso9660_quark(), 0,
- "error reading ISO file at lsn %lu",
- (long unsigned int) cur_block);
- return 0;
- }
- if (left_bytes < size) {
- readed = left_bytes;
- }
-
- is->offset += readed;
- }
- return readed;
-}
-
-static bool
-iso9660_input_eof(struct input_stream *is)
-{
- return is->offset == is->size;
-}
-
-/* exported structures */
-
-static const char *const iso9660_archive_extensions[] = {
- "iso",
- NULL
-};
-
-static const struct input_plugin iso9660_input_plugin = {
- .close = iso9660_input_close,
- .read = iso9660_input_read,
- .eof = iso9660_input_eof,
-};
-
-const struct archive_plugin iso9660_archive_plugin = {
- .name = "iso",
- .open = iso9660_archive_open,
- .scan_reset = iso9660_archive_scan_reset,
- .scan_next = iso9660_archive_scan_next,
- .open_stream = iso9660_archive_open_stream,
- .close = iso9660_archive_close,
- .suffixes = iso9660_archive_extensions
-};
diff --git a/src/archive/iso9660_archive_plugin.h b/src/archive/iso9660_archive_plugin.h
deleted file mode 100644
index 47dc6e474..000000000
--- a/src/archive/iso9660_archive_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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_ARCHIVE_ISO9660_H
-#define MPD_ARCHIVE_ISO9660_H
-
-extern const struct archive_plugin iso9660_archive_plugin;
-
-#endif
diff --git a/src/archive/zzip_archive_plugin.c b/src/archive/zzip_archive_plugin.c
deleted file mode 100644
index ad96b5f89..000000000
--- a/src/archive/zzip_archive_plugin.c
+++ /dev/null
@@ -1,246 +0,0 @@
-/*
- * 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.
- */
-
-/**
- * zip archive handling (requires zziplib)
- */
-
-#include "config.h"
-#include "archive/zzip_archive_plugin.h"
-#include "archive_api.h"
-#include "archive_api.h"
-#include "input_internal.h"
-#include "input_plugin.h"
-#include "refcount.h"
-
-#include <zzip/zzip.h>
-#include <glib.h>
-#include <string.h>
-
-struct zzip_archive {
- struct archive_file base;
-
- struct refcount ref;
-
- ZZIP_DIR *dir;
- GSList *list;
- GSList *iter;
-};
-
-static const struct input_plugin zzip_input_plugin;
-
-static inline GQuark
-zzip_quark(void)
-{
- return g_quark_from_static_string("zzip");
-}
-
-/* archive open && listing routine */
-
-static struct archive_file *
-zzip_archive_open(const char *pathname, GError **error_r)
-{
- struct zzip_archive *context = g_malloc(sizeof(*context));
- ZZIP_DIRENT dirent;
-
- archive_file_init(&context->base, &zzip_archive_plugin);
- refcount_init(&context->ref);
-
- // open archive
- context->list = NULL;
- context->dir = zzip_dir_open(pathname, NULL);
- if (context->dir == NULL) {
- g_set_error(error_r, zzip_quark(), 0,
- "Failed to open ZIP file %s", pathname);
- return NULL;
- }
-
- while (zzip_dir_read(context->dir, &dirent)) {
- //add only files
- if (dirent.st_size > 0) {
- context->list = g_slist_prepend(context->list,
- g_strdup(dirent.d_name));
- }
- }
-
- return &context->base;
-}
-
-static void
-zzip_archive_scan_reset(struct archive_file *file)
-{
- struct zzip_archive *context = (struct zzip_archive *) file;
- //reset iterator
- context->iter = context->list;
-}
-
-static char *
-zzip_archive_scan_next(struct archive_file *file)
-{
- struct zzip_archive *context = (struct zzip_archive *) file;
- char *data = NULL;
- if (context->iter != NULL) {
- ///fetch data and goto next
- data = context->iter->data;
- context->iter = g_slist_next(context->iter);
- }
- return data;
-}
-
-static void
-zzip_archive_close(struct archive_file *file)
-{
- struct zzip_archive *context = (struct zzip_archive *) file;
-
- if (!refcount_dec(&context->ref))
- return;
-
- if (context->list) {
- //free list
- for (GSList *tmp = context->list; tmp != NULL; tmp = g_slist_next(tmp))
- g_free(tmp->data);
- g_slist_free(context->list);
- }
- //close archive
- zzip_dir_close (context->dir);
-
- g_free(context);
-}
-
-/* single archive handling */
-
-struct zzip_input_stream {
- struct input_stream base;
-
- struct zzip_archive *archive;
-
- ZZIP_FILE *file;
-};
-
-static struct input_stream *
-zzip_archive_open_stream(struct archive_file *file,
- const char *pathname,
- GMutex *mutex, GCond *cond,
- GError **error_r)
-{
- struct zzip_archive *context = (struct zzip_archive *) file;
- struct zzip_input_stream *zis;
- ZZIP_STAT z_stat;
-
- zis = g_new(struct zzip_input_stream, 1);
- input_stream_init(&zis->base, &zzip_input_plugin, pathname,
- mutex, cond);
-
- zis->archive = context;
- zis->file = zzip_file_open(context->dir, pathname, 0);
- if (zis->file == NULL) {
- g_free(zis);
- g_set_error(error_r, zzip_quark(), 0,
- "not found in the ZIP file: %s", pathname);
- return NULL;
- }
-
- zis->base.ready = true;
- //we are seekable (but its not recommendent to do so)
- zis->base.seekable = true;
-
- zzip_file_stat(zis->file, &z_stat);
- zis->base.size = z_stat.st_size;
-
- refcount_inc(&context->ref);
-
- return &zis->base;
-}
-
-static void
-zzip_input_close(struct input_stream *is)
-{
- struct zzip_input_stream *zis = (struct zzip_input_stream *)is;
-
- zzip_file_close(zis->file);
- zzip_archive_close(&zis->archive->base);
- input_stream_deinit(&zis->base);
- g_free(zis);
-}
-
-static size_t
-zzip_input_read(struct input_stream *is, void *ptr, size_t size,
- GError **error_r)
-{
- struct zzip_input_stream *zis = (struct zzip_input_stream *)is;
- int ret;
-
- ret = zzip_file_read(zis->file, ptr, size);
- if (ret < 0) {
- g_set_error(error_r, zzip_quark(), ret,
- "zzip_file_read() has failed");
- return 0;
- }
-
- is->offset = zzip_tell(zis->file);
-
- return ret;
-}
-
-static bool
-zzip_input_eof(struct input_stream *is)
-{
- struct zzip_input_stream *zis = (struct zzip_input_stream *)is;
-
- return (goffset)zzip_tell(zis->file) == is->size;
-}
-
-static bool
-zzip_input_seek(struct input_stream *is,
- goffset offset, int whence, GError **error_r)
-{
- struct zzip_input_stream *zis = (struct zzip_input_stream *)is;
- zzip_off_t ofs = zzip_seek(zis->file, offset, whence);
- if (ofs != -1) {
- g_set_error(error_r, zzip_quark(), ofs,
- "zzip_seek() has failed");
- is->offset = ofs;
- return true;
- }
- return false;
-}
-
-/* exported structures */
-
-static const char *const zzip_archive_extensions[] = {
- "zip",
- NULL
-};
-
-static const struct input_plugin zzip_input_plugin = {
- .close = zzip_input_close,
- .read = zzip_input_read,
- .eof = zzip_input_eof,
- .seek = zzip_input_seek,
-};
-
-const struct archive_plugin zzip_archive_plugin = {
- .name = "zzip",
- .open = zzip_archive_open,
- .scan_reset = zzip_archive_scan_reset,
- .scan_next = zzip_archive_scan_next,
- .open_stream = zzip_archive_open_stream,
- .close = zzip_archive_close,
- .suffixes = zzip_archive_extensions
-};
diff --git a/src/archive/zzip_archive_plugin.h b/src/archive/zzip_archive_plugin.h
deleted file mode 100644
index 2b2c01e5a..000000000
--- a/src/archive/zzip_archive_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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_ARCHIVE_ZZIP_H
-#define MPD_ARCHIVE_ZZIP_H
-
-extern const struct archive_plugin zzip_archive_plugin;
-
-#endif
diff --git a/src/archive_api.c b/src/archive_api.c
deleted file mode 100644
index be3c35f7e..000000000
--- a/src/archive_api.c
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h" /* must be first for large file support */
-#include "archive_api.h"
-
-#include <stdio.h>
-
-#include <string.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <errno.h>
-#include <glib.h>
-
-/**
- *
- * archive_lookup is used to determine if part of pathname refers to an regular
- * file (archive). If so then its also used to split pathname into archive file
- * and path used to locate file in archive. It also returns suffix of the file.
- * How it works:
- * We do stat of the parent of input pathname as long as we find an regular file
- * Normally this should never happen. When routine returns true pathname modified
- * and split into archive, inpath and suffix. Otherwise nothing happens
- *
- * For example:
- *
- * /music/path/Talco.zip/Talco - Combat Circus/12 - A la pachenka.mp3
- * is split into archive: /music/path/Talco.zip
- * inarchive pathname: Talco - Combat Circus/12 - A la pachenka.mp3
- * and suffix: zip
- */
-
-bool archive_lookup(char *pathname, char **archive, char **inpath, char **suffix)
-{
- char *pathdupe;
- int len, idx;
- struct stat st_info;
- bool ret = false;
-
- *archive = NULL;
- *inpath = NULL;
- *suffix = NULL;
-
- pathdupe = g_strdup(pathname);
- len = idx = strlen(pathname);
-
- while (idx > 0) {
- //try to stat if its real directory
- if (stat(pathdupe, &st_info) == -1) {
- if (errno != ENOTDIR) {
- g_warning("stat %s failed (errno=%d)\n", pathdupe, errno);
- break;
- }
- } else {
- //is something found ins original path (is not an archive)
- if (idx == len) {
- break;
- }
- //its a file ?
- if (S_ISREG(st_info.st_mode)) {
- //so the upper should be file
- pathname[idx] = 0;
- ret = true;
- *archive = pathname;
- *inpath = pathname + idx+1;
-
- //try to get suffix
- *suffix = NULL;
- while (idx > 0) {
- if (pathname[idx] == '.') {
- *suffix = pathname + idx + 1;
- break;
- }
- idx--;
- }
- break;
- } else {
- g_warning("not a regular file %s\n", pathdupe);
- break;
- }
- }
- //find one dir up
- while (idx > 0) {
- if (pathdupe[idx] == '/') {
- pathdupe[idx] = 0;
- break;
- }
- idx--;
- }
- }
- g_free(pathdupe);
- return ret;
-}
-
diff --git a/src/archive_api.h b/src/archive_api.h
deleted file mode 100644
index 4e0f603f5..000000000
--- a/src/archive_api.h
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * 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_ARCHIVE_API_H
-#define MPD_ARCHIVE_API_H
-
-/*
- * This is the public API which is used by archive plugins to
- * provide transparent archive decompression layer for mpd
- *
- */
-
-#include "archive_internal.h"
-#include "archive_plugin.h"
-#include "input_stream.h"
-
-#include <stdbool.h>
-
-bool archive_lookup(char *pathname, char **archive, char **inpath, char **suffix);
-
-#endif
-
diff --git a/src/archive_internal.h b/src/archive_internal.h
deleted file mode 100644
index 0d885e91c..000000000
--- a/src/archive_internal.h
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * 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_ARCHIVE_INTERNAL_H
-#define MPD_ARCHIVE_INTERNAL_H
-
-struct archive_file {
- const struct archive_plugin *plugin;
-};
-
-static inline void
-archive_file_init(struct archive_file *archive_file,
- const struct archive_plugin *plugin)
-{
- archive_file->plugin = plugin;
-}
-
-#endif
diff --git a/src/archive_list.c b/src/archive_list.c
deleted file mode 100644
index e23567bdb..000000000
--- a/src/archive_list.c
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "archive_list.h"
-#include "archive_plugin.h"
-#include "string_util.h"
-#include "archive/bz2_archive_plugin.h"
-#include "archive/iso9660_archive_plugin.h"
-#include "archive/zzip_archive_plugin.h"
-
-#include <string.h>
-#include <glib.h>
-
-const struct archive_plugin *const archive_plugins[] = {
-#ifdef HAVE_BZ2
- &bz2_archive_plugin,
-#endif
-#ifdef HAVE_ZZIP
- &zzip_archive_plugin,
-#endif
-#ifdef HAVE_ISO9660
- &iso9660_archive_plugin,
-#endif
- NULL
-};
-
-/** which plugins have been initialized successfully? */
-static bool archive_plugins_enabled[G_N_ELEMENTS(archive_plugins) - 1];
-
-#define archive_plugins_for_each_enabled(plugin) \
- archive_plugins_for_each(plugin) \
- if (archive_plugins_enabled[archive_plugin_iterator - archive_plugins])
-
-const struct archive_plugin *
-archive_plugin_from_suffix(const char *suffix)
-{
- if (suffix == NULL)
- return NULL;
-
- archive_plugins_for_each_enabled(plugin)
- if (plugin->suffixes != NULL &&
- string_array_contains(plugin->suffixes, suffix))
- return plugin;
-
- return NULL;
-}
-
-const struct archive_plugin *
-archive_plugin_from_name(const char *name)
-{
- archive_plugins_for_each_enabled(plugin)
- if (strcmp(plugin->name, name) == 0)
- return plugin;
-
- return NULL;
-}
-
-void archive_plugin_init_all(void)
-{
- for (unsigned i = 0; archive_plugins[i] != NULL; ++i) {
- const struct archive_plugin *plugin = archive_plugins[i];
- if (plugin->init == NULL || archive_plugins[i]->init())
- archive_plugins_enabled[i] = true;
- }
-}
-
-void archive_plugin_deinit_all(void)
-{
- archive_plugins_for_each_enabled(plugin)
- if (plugin->finish != NULL)
- plugin->finish();
-}
-
diff --git a/src/archive_list.h b/src/archive_list.h
deleted file mode 100644
index f944583ed..000000000
--- a/src/archive_list.h
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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_ARCHIVE_LIST_H
-#define MPD_ARCHIVE_LIST_H
-
-struct archive_plugin;
-
-extern const struct archive_plugin *const archive_plugins[];
-
-#define archive_plugins_for_each(plugin) \
- for (const struct archive_plugin *plugin, \
- *const*archive_plugin_iterator = &archive_plugins[0]; \
- (plugin = *archive_plugin_iterator) != NULL; \
- ++archive_plugin_iterator)
-
-/* interface for using plugins */
-
-const struct archive_plugin *
-archive_plugin_from_suffix(const char *suffix);
-
-const struct archive_plugin *
-archive_plugin_from_name(const char *name);
-
-/* this is where we "load" all the "plugins" ;-) */
-void archive_plugin_init_all(void);
-
-/* this is where we "unload" all the "plugins" */
-void archive_plugin_deinit_all(void);
-
-#endif
diff --git a/src/archive_plugin.c b/src/archive_plugin.c
deleted file mode 100644
index cf23e6393..000000000
--- a/src/archive_plugin.c
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * 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.
- */
-
-#include "archive_plugin.h"
-#include "archive_internal.h"
-
-#include <assert.h>
-
-struct archive_file *
-archive_file_open(const struct archive_plugin *plugin, const char *path,
- GError **error_r)
-{
- struct archive_file *file;
-
- assert(plugin != NULL);
- assert(plugin->open != NULL);
- assert(path != NULL);
- assert(error_r == NULL || *error_r == NULL);
-
- file = plugin->open(path, error_r);
-
- if (file != NULL) {
- assert(file->plugin != NULL);
- assert(file->plugin->close != NULL);
- assert(file->plugin->scan_reset != NULL);
- assert(file->plugin->scan_next != NULL);
- assert(file->plugin->open_stream != NULL);
- assert(error_r == NULL || *error_r == NULL);
- } else {
- assert(error_r == NULL || *error_r != NULL);
- }
-
- return file;
-}
-
-void
-archive_file_close(struct archive_file *file)
-{
- assert(file != NULL);
- assert(file->plugin != NULL);
- assert(file->plugin->close != NULL);
-
- file->plugin->close(file);
-}
-
-void
-archive_file_scan_reset(struct archive_file *file)
-{
- assert(file != NULL);
- assert(file->plugin != NULL);
- assert(file->plugin->scan_reset != NULL);
- assert(file->plugin->scan_next != NULL);
-
- file->plugin->scan_reset(file);
-}
-
-char *
-archive_file_scan_next(struct archive_file *file)
-{
- assert(file != NULL);
- assert(file->plugin != NULL);
- assert(file->plugin->scan_next != NULL);
-
- return file->plugin->scan_next(file);
-}
-
-struct input_stream *
-archive_file_open_stream(struct archive_file *file, const char *path,
- GMutex *mutex, GCond *cond,
- GError **error_r)
-{
- assert(file != NULL);
- assert(file->plugin != NULL);
- assert(file->plugin->open_stream != NULL);
-
- return file->plugin->open_stream(file, path, mutex, cond,
- error_r);
-}
diff --git a/src/archive_plugin.h b/src/archive_plugin.h
deleted file mode 100644
index b7b92446d..000000000
--- a/src/archive_plugin.h
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * 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_ARCHIVE_PLUGIN_H
-#define MPD_ARCHIVE_PLUGIN_H
-
-#include <glib.h>
-
-#include <stdbool.h>
-
-struct input_stream;
-struct archive_file;
-
-struct archive_plugin {
- const char *name;
-
- /**
- * optional, set this to NULL if the archive plugin doesn't
- * have/need one this must false if there is an error and
- * true otherwise
- */
- bool (*init)(void);
-
- /**
- * optional, set this to NULL if the archive plugin doesn't
- * have/need one
- */
- void (*finish)(void);
-
- /**
- * tryes to open archive file and associates handle with archive
- * returns pointer to handle used is all operations with this archive
- * or NULL when opening fails
- */
- struct archive_file *(*open)(const char *path_fs, GError **error_r);
-
- /**
- * reset routine will move current read index in archive to default
- * position and then the filenames from archives can be read
- * via scan_next routine
- */
- void (*scan_reset)(struct archive_file *);
-
- /**
- * the read method will return corresponding files from archive
- * (as pathnames) and move read index to next file. When there is no
- * next file it return NULL.
- */
- char *(*scan_next)(struct archive_file *);
-
- /**
- * Opens an input_stream of a file within the archive.
- *
- * @param path the path within the archive
- * @param error_r location to store the error occurring, or
- * NULL to ignore errors
- */
- struct input_stream *(*open_stream)(struct archive_file *af,
- const char *path,
- GMutex *mutex, GCond *cond,
- GError **error_r);
-
- /**
- * closes archive file.
- */
- void (*close)(struct archive_file *);
-
- /**
- * suffixes handled by this plugin.
- * last element in these arrays must always be a NULL
- */
- const char *const*suffixes;
-};
-
-struct archive_file *
-archive_file_open(const struct archive_plugin *plugin, const char *path,
- GError **error_r);
-
-void
-archive_file_close(struct archive_file *file);
-
-void
-archive_file_scan_reset(struct archive_file *file);
-
-char *
-archive_file_scan_next(struct archive_file *file);
-
-struct input_stream *
-archive_file_open_stream(struct archive_file *file, const char *path,
- GMutex *mutex, GCond *cond,
- GError **error_r);
-
-#endif
diff --git a/src/audio_check.c b/src/audio_check.c
deleted file mode 100644
index a9aa2dd82..000000000
--- a/src/audio_check.c
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * 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.
- */
-
-#include "audio_check.h"
-#include "audio_format.h"
-
-#include <assert.h>
-
-bool
-audio_check_sample_rate(unsigned long sample_rate, GError **error_r)
-{
- if (!audio_valid_sample_rate(sample_rate)) {
- g_set_error(error_r, audio_format_quark(), 0,
- "Invalid sample rate: %lu", sample_rate);
- return false;
- }
-
- return true;
-}
-
-bool
-audio_check_sample_format(enum sample_format sample_format, GError **error_r)
-{
- if (!audio_valid_sample_format(sample_format)) {
- g_set_error(error_r, audio_format_quark(), 0,
- "Invalid sample format: %u", sample_format);
- return false;
- }
-
- return true;
-}
-
-bool
-audio_check_channel_count(unsigned channels, GError **error_r)
-{
- if (!audio_valid_channel_count(channels)) {
- g_set_error(error_r, audio_format_quark(), 0,
- "Invalid channel count: %u", channels);
- return false;
- }
-
- return true;
-}
-
-bool
-audio_format_init_checked(struct audio_format *af, unsigned long sample_rate,
- enum sample_format sample_format, unsigned channels,
- GError **error_r)
-{
- if (audio_check_sample_rate(sample_rate, error_r) &&
- audio_check_sample_format(sample_format, error_r) &&
- audio_check_channel_count(channels, error_r)) {
- audio_format_init(af, sample_rate, sample_format, channels);
- assert(audio_format_valid(af));
- return true;
- } else
- return false;
-}
diff --git a/src/audio_check.h b/src/audio_check.h
deleted file mode 100644
index 9f71cf9c0..000000000
--- a/src/audio_check.h
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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_AUDIO_CHECK_H
-#define MPD_AUDIO_CHECK_H
-
-#include "audio_format.h"
-
-#include <glib.h>
-#include <stdbool.h>
-
-/**
- * The GLib quark used for errors reported by this library.
- */
-G_GNUC_CONST
-static inline GQuark
-audio_format_quark(void)
-{
- return g_quark_from_static_string("audio_format");
-}
-
-bool
-audio_check_sample_rate(unsigned long sample_rate, GError **error_r);
-
-bool
-audio_check_sample_format(enum sample_format, GError **error_r);
-
-bool
-audio_check_channel_count(unsigned sample_format, GError **error_r);
-
-/**
- * Wrapper for audio_format_init(), which checks all attributes.
- */
-bool
-audio_format_init_checked(struct audio_format *af, unsigned long sample_rate,
- enum sample_format sample_format, unsigned channels,
- GError **error_r);
-
-#endif
diff --git a/src/audio_config.c b/src/audio_config.c
deleted file mode 100644
index 72869c384..000000000
--- a/src/audio_config.c
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "audio_config.h"
-#include "audio_format.h"
-#include "audio_parser.h"
-#include "output_internal.h"
-#include "output_plugin.h"
-#include "output_all.h"
-#include "conf.h"
-#include "mpd_error.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <stdlib.h>
-
-static struct audio_format configured_audio_format;
-
-void getOutputAudioFormat(const struct audio_format *inAudioFormat,
- struct audio_format *outAudioFormat)
-{
- *outAudioFormat = *inAudioFormat;
- audio_format_mask_apply(outAudioFormat, &configured_audio_format);
-}
-
-void initAudioConfig(void)
-{
- const struct config_param *param = config_get_param(CONF_AUDIO_OUTPUT_FORMAT);
- GError *error = NULL;
- bool ret;
-
- if (param == NULL)
- return;
-
- ret = audio_format_parse(&configured_audio_format, param->value,
- true, &error);
- if (!ret)
- MPD_ERROR("error parsing \"%s\" at line %i: %s",
- CONF_AUDIO_OUTPUT_FORMAT, param->line,
- error->message);
-}
diff --git a/src/audio_config.h b/src/audio_config.h
deleted file mode 100644
index 85143247f..000000000
--- a/src/audio_config.h
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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_AUDIO_CONFIG_H
-#define MPD_AUDIO_CONFIG_H
-
-#include <stdbool.h>
-
-struct audio_format;
-
-void getOutputAudioFormat(const struct audio_format *inFormat,
- struct audio_format *outFormat);
-
-/* make sure initPlayerData is called before this function!! */
-void initAudioConfig(void);
-
-#endif
diff --git a/src/audio_format.c b/src/audio_format.c
deleted file mode 100644
index 45d94a853..000000000
--- a/src/audio_format.c
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * 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.
- */
-
-#include "audio_format.h"
-
-#include <assert.h>
-#include <stdio.h>
-
-void
-audio_format_mask_apply(struct audio_format *af,
- const struct audio_format *mask)
-{
- assert(audio_format_valid(af));
- assert(audio_format_mask_valid(mask));
-
- if (mask->sample_rate != 0)
- af->sample_rate = mask->sample_rate;
-
- if (mask->format != SAMPLE_FORMAT_UNDEFINED)
- af->format = mask->format;
-
- if (mask->channels != 0)
- af->channels = mask->channels;
-
- assert(audio_format_valid(af));
-}
-
-const char *
-sample_format_to_string(enum sample_format format)
-{
- switch (format) {
- case SAMPLE_FORMAT_UNDEFINED:
- return "?";
-
- case SAMPLE_FORMAT_S8:
- return "8";
-
- case SAMPLE_FORMAT_S16:
- return "16";
-
- case SAMPLE_FORMAT_S24_P32:
- return "24";
-
- case SAMPLE_FORMAT_S32:
- return "32";
-
- case SAMPLE_FORMAT_FLOAT:
- return "f";
-
- case SAMPLE_FORMAT_DSD:
- return "dsd";
- }
-
- /* unreachable */
- assert(false);
- return "?";
-}
-
-const char *
-audio_format_to_string(const struct audio_format *af,
- struct audio_format_string *s)
-{
- assert(af != NULL);
- assert(s != NULL);
-
- snprintf(s->buffer, sizeof(s->buffer), "%u:%s:%u",
- af->sample_rate, sample_format_to_string(af->format),
- af->channels);
-
- return s->buffer;
-}
diff --git a/src/audio_format.h b/src/audio_format.h
deleted file mode 100644
index bf77add3b..000000000
--- a/src/audio_format.h
+++ /dev/null
@@ -1,307 +0,0 @@
-/*
- * 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_AUDIO_FORMAT_H
-#define MPD_AUDIO_FORMAT_H
-
-#include <glib.h>
-#include <stdint.h>
-#include <stdbool.h>
-#include <assert.h>
-
-enum sample_format {
- SAMPLE_FORMAT_UNDEFINED = 0,
-
- SAMPLE_FORMAT_S8,
- SAMPLE_FORMAT_S16,
-
- /**
- * Signed 24 bit integer samples, packed in 32 bit integers
- * (the most significant byte is filled with the sign bit).
- */
- SAMPLE_FORMAT_S24_P32,
-
- SAMPLE_FORMAT_S32,
-
- /**
- * 32 bit floating point samples in the host's format. The
- * range is -1.0f to +1.0f.
- */
- SAMPLE_FORMAT_FLOAT,
-
- /**
- * Direct Stream Digital. 1-bit samples; each frame has one
- * byte (8 samples) per channel.
- */
- SAMPLE_FORMAT_DSD,
-};
-
-static const unsigned MAX_CHANNELS = 8;
-
-/**
- * This structure describes the format of a raw PCM stream.
- */
-struct audio_format {
- /**
- * The sample rate in Hz. A better name for this attribute is
- * "frame rate", because technically, you have two samples per
- * frame in stereo sound.
- */
- uint32_t sample_rate;
-
- /**
- * The format samples are stored in. See the #sample_format
- * enum for valid values.
- */
- uint8_t format;
-
- /**
- * The number of channels. Only mono (1) and stereo (2) are
- * fully supported currently.
- */
- uint8_t channels;
-};
-
-/**
- * Buffer for audio_format_string().
- */
-struct audio_format_string {
- char buffer[24];
-};
-
-/**
- * Clears the #audio_format object, i.e. sets all attributes to an
- * undefined (invalid) value.
- */
-static inline void audio_format_clear(struct audio_format *af)
-{
- af->sample_rate = 0;
- af->format = SAMPLE_FORMAT_UNDEFINED;
- af->channels = 0;
-}
-
-/**
- * Initializes an #audio_format object, i.e. sets all
- * attributes to valid values.
- */
-static inline void audio_format_init(struct audio_format *af,
- uint32_t sample_rate,
- enum sample_format format, uint8_t channels)
-{
- af->sample_rate = sample_rate;
- af->format = (uint8_t)format;
- af->channels = channels;
-}
-
-/**
- * Checks whether the specified #audio_format object has a defined
- * value.
- */
-static inline bool audio_format_defined(const struct audio_format *af)
-{
- return af->sample_rate != 0;
-}
-
-/**
- * Checks whether the specified #audio_format object is full, i.e. all
- * attributes are defined. This is more complete than
- * audio_format_defined(), but slower.
- */
-static inline bool
-audio_format_fully_defined(const struct audio_format *af)
-{
- return af->sample_rate != 0 && af->format != SAMPLE_FORMAT_UNDEFINED &&
- af->channels != 0;
-}
-
-/**
- * Checks whether the specified #audio_format object has at least one
- * defined value.
- */
-static inline bool
-audio_format_mask_defined(const struct audio_format *af)
-{
- return af->sample_rate != 0 || af->format != SAMPLE_FORMAT_UNDEFINED ||
- af->channels != 0;
-}
-
-/**
- * Checks whether the sample rate is valid.
- *
- * @param sample_rate the sample rate in Hz
- */
-static inline bool
-audio_valid_sample_rate(unsigned sample_rate)
-{
- return sample_rate > 0 && sample_rate < (1 << 30);
-}
-
-/**
- * Checks whether the sample format is valid.
- *
- * @param bits the number of significant bits per sample
- */
-static inline bool
-audio_valid_sample_format(enum sample_format format)
-{
- switch (format) {
- case SAMPLE_FORMAT_S8:
- case SAMPLE_FORMAT_S16:
- case SAMPLE_FORMAT_S24_P32:
- case SAMPLE_FORMAT_S32:
- case SAMPLE_FORMAT_FLOAT:
- case SAMPLE_FORMAT_DSD:
- return true;
-
- case SAMPLE_FORMAT_UNDEFINED:
- break;
- }
-
- return false;
-}
-
-/**
- * Checks whether the number of channels is valid.
- */
-static inline bool
-audio_valid_channel_count(unsigned channels)
-{
- return channels >= 1 && channels <= MAX_CHANNELS;
-}
-
-/**
- * Returns false if the format is not valid for playback with MPD.
- * This function performs some basic validity checks.
- */
-G_GNUC_PURE
-static inline bool audio_format_valid(const struct audio_format *af)
-{
- return audio_valid_sample_rate(af->sample_rate) &&
- audio_valid_sample_format((enum sample_format)af->format) &&
- audio_valid_channel_count(af->channels);
-}
-
-/**
- * Returns false if the format mask is not valid for playback with
- * MPD. This function performs some basic validity checks.
- */
-G_GNUC_PURE
-static inline bool audio_format_mask_valid(const struct audio_format *af)
-{
- return (af->sample_rate == 0 ||
- audio_valid_sample_rate(af->sample_rate)) &&
- (af->format == SAMPLE_FORMAT_UNDEFINED ||
- audio_valid_sample_format((enum sample_format)af->format)) &&
- (af->channels == 0 || audio_valid_channel_count(af->channels));
-}
-
-static inline bool audio_format_equals(const struct audio_format *a,
- const struct audio_format *b)
-{
- return a->sample_rate == b->sample_rate &&
- a->format == b->format &&
- a->channels == b->channels;
-}
-
-void
-audio_format_mask_apply(struct audio_format *af,
- const struct audio_format *mask);
-
-G_GNUC_CONST
-static inline unsigned
-sample_format_size(enum sample_format format)
-{
- switch (format) {
- case SAMPLE_FORMAT_S8:
- return 1;
-
- case SAMPLE_FORMAT_S16:
- return 2;
-
- case SAMPLE_FORMAT_S24_P32:
- case SAMPLE_FORMAT_S32:
- case SAMPLE_FORMAT_FLOAT:
- return 4;
-
- case SAMPLE_FORMAT_DSD:
- /* each frame has 8 samples per channel */
- return 1;
-
- case SAMPLE_FORMAT_UNDEFINED:
- return 0;
- }
-
- assert(false);
- return 0;
-}
-
-/**
- * Returns the size of each (mono) sample in bytes.
- */
-G_GNUC_PURE
-static inline unsigned audio_format_sample_size(const struct audio_format *af)
-{
- return sample_format_size((enum sample_format)af->format);
-}
-
-/**
- * Returns the size of each full frame in bytes.
- */
-G_GNUC_PURE
-static inline unsigned
-audio_format_frame_size(const struct audio_format *af)
-{
- return audio_format_sample_size(af) * af->channels;
-}
-
-/**
- * Returns the floating point factor which converts a time span to a
- * storage size in bytes.
- */
-G_GNUC_PURE
-static inline double audio_format_time_to_size(const struct audio_format *af)
-{
- return af->sample_rate * audio_format_frame_size(af);
-}
-
-/**
- * Renders a #sample_format enum into a string, e.g. for printing it
- * in a log file.
- *
- * @param format a #sample_format enum value
- * @return the string
- */
-G_GNUC_PURE G_GNUC_MALLOC
-const char *
-sample_format_to_string(enum sample_format format);
-
-/**
- * Renders the #audio_format object into a string, e.g. for printing
- * it in a log file.
- *
- * @param af the #audio_format object
- * @param s a buffer to print into
- * @return the string, or NULL if the #audio_format object is invalid
- */
-G_GNUC_PURE G_GNUC_MALLOC
-const char *
-audio_format_to_string(const struct audio_format *af,
- struct audio_format_string *s);
-
-#endif
diff --git a/src/audio_parser.c b/src/audio_parser.c
deleted file mode 100644
index 152eab5d4..000000000
--- a/src/audio_parser.c
+++ /dev/null
@@ -1,222 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * Parser functions for audio related objects.
- *
- */
-
-#include "config.h"
-#include "audio_parser.h"
-#include "audio_format.h"
-#include "audio_check.h"
-#include "gcc.h"
-
-#include <assert.h>
-#include <string.h>
-#include <stdlib.h>
-
-/**
- * The GLib quark used for errors reported by this library.
- */
-static inline GQuark
-audio_parser_quark(void)
-{
- return g_quark_from_static_string("audio_parser");
-}
-
-static bool
-parse_sample_rate(const char *src, bool mask, uint32_t *sample_rate_r,
- const char **endptr_r, GError **error_r)
-{
- unsigned long value;
- char *endptr;
-
- if (mask && *src == '*') {
- *sample_rate_r = 0;
- *endptr_r = src + 1;
- return true;
- }
-
- value = strtoul(src, &endptr, 10);
- if (endptr == src) {
- g_set_error(error_r, audio_parser_quark(), 0,
- "Failed to parse the sample rate");
- return false;
- } else if (!audio_check_sample_rate(value, error_r))
- return false;
-
- *sample_rate_r = value;
- *endptr_r = endptr;
- return true;
-}
-
-static bool
-parse_sample_format(const char *src, bool mask,
- enum sample_format *sample_format_r,
- const char **endptr_r, GError **error_r)
-{
- unsigned long value;
- char *endptr;
- enum sample_format sample_format;
-
- if (mask && *src == '*') {
- *sample_format_r = SAMPLE_FORMAT_UNDEFINED;
- *endptr_r = src + 1;
- return true;
- }
-
- if (*src == 'f') {
- *sample_format_r = SAMPLE_FORMAT_FLOAT;
- *endptr_r = src + 1;
- return true;
- }
-
- if (memcmp(src, "dsd", 3) == 0) {
- *sample_format_r = SAMPLE_FORMAT_DSD;
- *endptr_r = src + 3;
- return true;
- }
-
- value = strtoul(src, &endptr, 10);
- if (endptr == src) {
- g_set_error(error_r, audio_parser_quark(), 0,
- "Failed to parse the sample format");
- return false;
- }
-
- switch (value) {
- case 8:
- sample_format = SAMPLE_FORMAT_S8;
- break;
-
- case 16:
- sample_format = SAMPLE_FORMAT_S16;
- break;
-
- case 24:
- if (memcmp(endptr, "_3", 2) == 0)
- /* for backwards compatibility */
- endptr += 2;
-
- sample_format = SAMPLE_FORMAT_S24_P32;
- break;
-
- case 32:
- sample_format = SAMPLE_FORMAT_S32;
- break;
-
- default:
- g_set_error(error_r, audio_parser_quark(), 0,
- "Invalid sample format: %lu", value);
- return false;
- }
-
- assert(audio_valid_sample_format(sample_format));
-
- *sample_format_r = sample_format;
- *endptr_r = endptr;
- return true;
-}
-
-static bool
-parse_channel_count(const char *src, bool mask, uint8_t *channels_r,
- const char **endptr_r, GError **error_r)
-{
- unsigned long value;
- char *endptr;
-
- if (mask && *src == '*') {
- *channels_r = 0;
- *endptr_r = src + 1;
- return true;
- }
-
- value = strtoul(src, &endptr, 10);
- if (endptr == src) {
- g_set_error(error_r, audio_parser_quark(), 0,
- "Failed to parse the channel count");
- return false;
- } else if (!audio_check_channel_count(value, error_r))
- return false;
-
- *channels_r = value;
- *endptr_r = endptr;
- return true;
-}
-
-bool
-audio_format_parse(struct audio_format *dest, const char *src,
- bool mask, GError **error_r)
-{
- uint32_t rate;
- enum sample_format sample_format;
- uint8_t channels;
-
- audio_format_clear(dest);
-
- /* parse sample rate */
-
-#if GCC_CHECK_VERSION(4,7)
- /* workaround -Wmaybe-uninitialized false positive */
- rate = 0;
-#endif
-
- if (!parse_sample_rate(src, mask, &rate, &src, error_r))
- return false;
-
- if (*src++ != ':') {
- g_set_error(error_r, audio_parser_quark(), 0,
- "Sample format missing");
- return false;
- }
-
- /* parse sample format */
-
-#if GCC_CHECK_VERSION(4,7)
- /* workaround -Wmaybe-uninitialized false positive */
- sample_format = SAMPLE_FORMAT_UNDEFINED;
-#endif
-
- if (!parse_sample_format(src, mask, &sample_format, &src, error_r))
- return false;
-
- if (*src++ != ':') {
- g_set_error(error_r, audio_parser_quark(), 0,
- "Channel count missing");
- return false;
- }
-
- /* parse channel count */
-
- if (!parse_channel_count(src, mask, &channels, &src, error_r))
- return false;
-
- if (*src != 0) {
- g_set_error(error_r, audio_parser_quark(), 0,
- "Extra data after channel count: %s", src);
- return false;
- }
-
- audio_format_init(dest, rate, sample_format, channels);
- assert(mask ? audio_format_mask_valid(dest)
- : audio_format_valid(dest));
-
- return true;
-}
diff --git a/src/audio_parser.h b/src/audio_parser.h
deleted file mode 100644
index a963eb467..000000000
--- a/src/audio_parser.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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.
- */
-
-/** \file
- *
- * Parser functions for audio related objects.
- */
-
-#ifndef AUDIO_PARSER_H
-#define AUDIO_PARSER_H
-
-#include <glib.h>
-
-#include <stdbool.h>
-
-struct audio_format;
-
-/**
- * Parses a string in the form "SAMPLE_RATE:BITS:CHANNELS" into an
- * #audio_format.
- *
- * @param dest the destination #audio_format struct
- * @param src the input string
- * @param mask if true, then "*" is allowed for any number of items
- * @param error_r location to store the error occurring, or NULL to
- * ignore errors
- * @return true on success
- */
-bool
-audio_format_parse(struct audio_format *dest, const char *src,
- bool mask, GError **error_r);
-
-#endif
diff --git a/src/buffer.c b/src/buffer.c
deleted file mode 100644
index 559f39a9a..000000000
--- a/src/buffer.c
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "buffer.h"
-#include "chunk.h"
-#include "poison.h"
-
-#include <glib.h>
-
-#include <assert.h>
-
-struct music_buffer {
- struct music_chunk *chunks;
- unsigned num_chunks;
-
- struct music_chunk *available;
-
- /** a mutex which protects #available */
- GMutex *mutex;
-
-#ifndef NDEBUG
- unsigned num_allocated;
-#endif
-};
-
-struct music_buffer *
-music_buffer_new(unsigned num_chunks)
-{
- struct music_buffer *buffer;
- struct music_chunk *chunk;
-
- assert(num_chunks > 0);
-
- buffer = g_new(struct music_buffer, 1);
-
- buffer->chunks = g_new(struct music_chunk, num_chunks);
- buffer->num_chunks = num_chunks;
-
- chunk = buffer->available = buffer->chunks;
- poison_undefined(chunk, sizeof(*chunk));
-
- for (unsigned i = 1; i < num_chunks; ++i) {
- chunk->next = &buffer->chunks[i];
- chunk = chunk->next;
- poison_undefined(chunk, sizeof(*chunk));
- }
-
- chunk->next = NULL;
-
- buffer->mutex = g_mutex_new();
-
-#ifndef NDEBUG
- buffer->num_allocated = 0;
-#endif
-
- return buffer;
-}
-
-void
-music_buffer_free(struct music_buffer *buffer)
-{
- assert(buffer->chunks != NULL);
- assert(buffer->num_chunks > 0);
- assert(buffer->num_allocated == 0);
-
- g_mutex_free(buffer->mutex);
- g_free(buffer->chunks);
- g_free(buffer);
-}
-
-unsigned
-music_buffer_size(const struct music_buffer *buffer)
-{
- return buffer->num_chunks;
-}
-
-struct music_chunk *
-music_buffer_allocate(struct music_buffer *buffer)
-{
- struct music_chunk *chunk;
-
- g_mutex_lock(buffer->mutex);
-
- chunk = buffer->available;
- if (chunk != NULL) {
- buffer->available = chunk->next;
- music_chunk_init(chunk);
-
-#ifndef NDEBUG
- ++buffer->num_allocated;
-#endif
- }
-
- g_mutex_unlock(buffer->mutex);
- return chunk;
-}
-
-void
-music_buffer_return(struct music_buffer *buffer, struct music_chunk *chunk)
-{
- assert(buffer != NULL);
- assert(chunk != NULL);
-
- if (chunk->other != NULL)
- music_buffer_return(buffer, chunk->other);
-
- g_mutex_lock(buffer->mutex);
-
- music_chunk_free(chunk);
- poison_undefined(chunk, sizeof(*chunk));
-
- chunk->next = buffer->available;
- buffer->available = chunk;
-
-#ifndef NDEBUG
- --buffer->num_allocated;
-#endif
-
- g_mutex_unlock(buffer->mutex);
-}
diff --git a/src/buffer.h b/src/buffer.h
deleted file mode 100644
index f860231e7..000000000
--- a/src/buffer.h
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * 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_MUSIC_BUFFER_H
-#define MPD_MUSIC_BUFFER_H
-
-/**
- * An allocator for #music_chunk objects.
- */
-struct music_buffer;
-
-/**
- * Creates a new #music_buffer object.
- *
- * @param num_chunks the number of #music_chunk reserved in this
- * buffer
- */
-struct music_buffer *
-music_buffer_new(unsigned num_chunks);
-
-/**
- * Frees the #music_buffer object
- */
-void
-music_buffer_free(struct music_buffer *buffer);
-
-/**
- * Returns the total number of reserved chunks in this buffer. This
- * is the same value which was passed to the constructor
- * music_buffer_new().
- */
-unsigned
-music_buffer_size(const struct music_buffer *buffer);
-
-/**
- * Allocates a chunk from the buffer. When it is not used anymore,
- * call music_buffer_return().
- *
- * @return an empty chunk or NULL if there are no chunks available
- */
-struct music_chunk *
-music_buffer_allocate(struct music_buffer *buffer);
-
-/**
- * Returns a chunk to the buffer. It can be reused by
- * music_buffer_allocate() then.
- */
-void
-music_buffer_return(struct music_buffer *buffer, struct music_chunk *chunk);
-
-#endif
diff --git a/src/chunk.c b/src/chunk.c
deleted file mode 100644
index 1eb96f4b9..000000000
--- a/src/chunk.c
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "chunk.h"
-#include "audio_format.h"
-#include "tag.h"
-
-#include <assert.h>
-
-void
-music_chunk_init(struct music_chunk *chunk)
-{
- chunk->other = NULL;
- chunk->length = 0;
- chunk->tag = NULL;
- chunk->replay_gain_serial = 0;
-}
-
-void
-music_chunk_free(struct music_chunk *chunk)
-{
- if (chunk->tag != NULL)
- tag_free(chunk->tag);
-}
-
-#ifndef NDEBUG
-bool
-music_chunk_check_format(const struct music_chunk *chunk,
- const struct audio_format *audio_format)
-{
- assert(chunk != NULL);
- assert(audio_format != NULL);
- assert(audio_format_valid(audio_format));
-
- return chunk->length == 0 ||
- audio_format_equals(&chunk->audio_format, audio_format);
-}
-#endif
-
-void *
-music_chunk_write(struct music_chunk *chunk,
- const struct audio_format *audio_format,
- float data_time, uint16_t bit_rate,
- size_t *max_length_r)
-{
- const size_t frame_size = audio_format_frame_size(audio_format);
- size_t num_frames;
-
- assert(music_chunk_check_format(chunk, audio_format));
- assert(chunk->length == 0 || audio_format_valid(&chunk->audio_format));
-
- if (chunk->length == 0) {
- /* if the chunk is empty, nobody has set bitRate and
- times yet */
-
- chunk->bit_rate = bit_rate;
- chunk->times = data_time;
- }
-
- num_frames = (sizeof(chunk->data) - chunk->length) / frame_size;
- if (num_frames == 0)
- return NULL;
-
-#ifndef NDEBUG
- chunk->audio_format = *audio_format;
-#endif
-
- *max_length_r = num_frames * frame_size;
- return chunk->data + chunk->length;
-}
-
-bool
-music_chunk_expand(struct music_chunk *chunk,
- const struct audio_format *audio_format, size_t length)
-{
- const size_t frame_size = audio_format_frame_size(audio_format);
-
- assert(chunk != NULL);
- assert(chunk->length + length <= sizeof(chunk->data));
- assert(audio_format_equals(&chunk->audio_format, audio_format));
-
- chunk->length += length;
-
- return chunk->length + frame_size > sizeof(chunk->data);
-}
diff --git a/src/chunk.h b/src/chunk.h
deleted file mode 100644
index a06a203eb..000000000
--- a/src/chunk.h
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * 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_CHUNK_H
-#define MPD_CHUNK_H
-
-#include "replay_gain_info.h"
-
-#ifndef NDEBUG
-#include "audio_format.h"
-#endif
-
-#include <stdbool.h>
-#include <stdint.h>
-#include <stddef.h>
-
-enum {
- CHUNK_SIZE = 4096,
-};
-
-struct audio_format;
-
-/**
- * A chunk of music data. Its format is defined by the
- * music_pipe_append() caller.
- */
-struct music_chunk {
- /** the next chunk in a linked list */
- struct music_chunk *next;
-
- /**
- * An optional chunk which should be mixed into this chunk.
- * This is used for cross-fading.
- */
- struct music_chunk *other;
-
- /**
- * The current mix ratio for cross-fading: 1.0 means play 100%
- * of this chunk, 0.0 means play 100% of the "other" chunk.
- */
- float mix_ratio;
-
- /** number of bytes stored in this chunk */
- uint16_t length;
-
- /** current bit rate of the source file */
- uint16_t bit_rate;
-
- /** the time stamp within the song */
- float times;
-
- /**
- * An optional tag associated with this chunk (and the
- * following chunks); appears at song boundaries. The tag
- * object is owned by this chunk, and must be freed when this
- * chunk is deinitialized in music_chunk_free()
- */
- struct tag *tag;
-
- /**
- * Replay gain information associated with this chunk.
- * Only valid if the serial is not 0.
- */
- struct replay_gain_info replay_gain_info;
-
- /**
- * A serial number for checking if replay gain info has
- * changed since the last chunk. The magic value 0 indicates
- * that there is no replay gain info available.
- */
- unsigned replay_gain_serial;
-
- /** the data (probably PCM) */
- char data[CHUNK_SIZE];
-
-#ifndef NDEBUG
- struct audio_format audio_format;
-#endif
-};
-
-void
-music_chunk_init(struct music_chunk *chunk);
-
-void
-music_chunk_free(struct music_chunk *chunk);
-
-static inline bool
-music_chunk_is_empty(const struct music_chunk *chunk)
-{
- return chunk->length == 0 && chunk->tag == NULL;
-}
-
-#ifndef NDEBUG
-/**
- * Checks if the audio format if the chunk is equal to the specified
- * audio_format.
- */
-bool
-music_chunk_check_format(const struct music_chunk *chunk,
- const struct audio_format *audio_format);
-#endif
-
-/**
- * Prepares appending to the music chunk. Returns a buffer where you
- * may write into. After you are finished, call music_chunk_expand().
- *
- * @param chunk the music_chunk object
- * @param audio_format the audio format for the appended data; must
- * stay the same for the life cycle of this chunk
- * @param data_time the time within the song
- * @param bit_rate the current bit rate of the source file
- * @param max_length_r the maximum write length is returned here
- * @return a writable buffer, or NULL if the chunk is full
- */
-void *
-music_chunk_write(struct music_chunk *chunk,
- const struct audio_format *audio_format,
- float data_time, uint16_t bit_rate,
- size_t *max_length_r);
-
-/**
- * Increases the length of the chunk after the caller has written to
- * the buffer returned by music_chunk_write().
- *
- * @param chunk the music_chunk object
- * @param audio_format the audio format for the appended data; must
- * stay the same for the life cycle of this chunk
- * @param length the number of bytes which were appended
- * @return true if the chunk is full
- */
-bool
-music_chunk_expand(struct music_chunk *chunk,
- const struct audio_format *audio_format, size_t length);
-
-#endif
diff --git a/src/client.c b/src/client.c
deleted file mode 100644
index 3fa2c9be4..000000000
--- a/src/client.c
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "client_internal.h"
-
-bool client_is_expired(const struct client *client)
-{
- return client->channel == NULL;
-}
-
-int client_get_uid(const struct client *client)
-{
- return client->uid;
-}
-
-unsigned client_get_permission(const struct client *client)
-{
- return client->permission;
-}
-
-void client_set_permission(struct client *client, unsigned permission)
-{
- client->permission = permission;
-}
diff --git a/src/client.h b/src/client.h
deleted file mode 100644
index 0302a2e0a..000000000
--- a/src/client.h
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * 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_CLIENT_H
-#define MPD_CLIENT_H
-
-#include <glib.h>
-#include <stdbool.h>
-#include <stddef.h>
-#include <stdarg.h>
-
-struct client;
-struct sockaddr;
-struct player_control;
-
-void client_manager_init(void);
-void client_manager_deinit(void);
-
-void client_new(struct player_control *player_control,
- int fd, const struct sockaddr *sa, size_t sa_length, int uid);
-
-G_GNUC_PURE
-bool client_is_expired(const struct client *client);
-
-/**
- * returns the uid of the client process, or a negative value if the
- * uid is unknown
- */
-G_GNUC_PURE
-int client_get_uid(const struct client *client);
-
-/**
- * Is this client running on the same machine, connected with a local
- * (UNIX domain) socket?
- */
-G_GNUC_PURE
-static inline bool
-client_is_local(const struct client *client)
-{
- return client_get_uid(client) > 0;
-}
-
-G_GNUC_PURE
-unsigned client_get_permission(const struct client *client);
-
-void client_set_permission(struct client *client, unsigned permission);
-
-/**
- * Write a C string to the client.
- */
-void client_puts(struct client *client, const char *s);
-
-/**
- * Write a printf-like formatted string to the client.
- */
-void client_vprintf(struct client *client, const char *fmt, va_list args);
-
-/**
- * Write a printf-like formatted string to the client.
- */
-G_GNUC_PRINTF(2, 3) void client_printf(struct client *client, const char *fmt, ...);
-
-#endif
diff --git a/src/client_event.c b/src/client_event.c
deleted file mode 100644
index 4f54ae0a7..000000000
--- a/src/client_event.c
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "client_internal.h"
-#include "main.h"
-
-#include <assert.h>
-
-static gboolean
-client_out_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition,
- gpointer data)
-{
- struct client *client = data;
-
- assert(!client_is_expired(client));
-
- if (condition != G_IO_OUT) {
- client_set_expired(client);
- return false;
- }
-
- client_write_deferred(client);
-
- if (client_is_expired(client)) {
- client_close(client);
- return false;
- }
-
- g_timer_start(client->last_activity);
-
- if (g_queue_is_empty(client->deferred_send)) {
- /* done sending deferred buffers exist: schedule
- read */
- client->source_id = g_io_add_watch(client->channel,
- G_IO_IN|G_IO_ERR|G_IO_HUP,
- client_in_event, client);
- return false;
- }
-
- /* write more */
- return true;
-}
-
-gboolean
-client_in_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition,
- gpointer data)
-{
- struct client *client = data;
- enum command_return ret;
-
- assert(!client_is_expired(client));
-
- if (condition != G_IO_IN) {
- client_set_expired(client);
- return false;
- }
-
- g_timer_start(client->last_activity);
-
- ret = client_read(client);
- switch (ret) {
- case COMMAND_RETURN_OK:
- case COMMAND_RETURN_ERROR:
- break;
-
- case COMMAND_RETURN_KILL:
- client_close(client);
- g_main_loop_quit(main_loop);
- return false;
-
- case COMMAND_RETURN_CLOSE:
- client_close(client);
- return false;
- }
-
- if (client_is_expired(client)) {
- client_close(client);
- return false;
- }
-
- if (!g_queue_is_empty(client->deferred_send)) {
- /* deferred buffers exist: schedule write */
- client->source_id = g_io_add_watch(client->channel,
- G_IO_OUT|G_IO_ERR|G_IO_HUP,
- client_out_event, client);
- return false;
- }
-
- /* read more */
- return true;
-}
diff --git a/src/client_expire.c b/src/client_expire.c
deleted file mode 100644
index 1ca32ebcc..000000000
--- a/src/client_expire.c
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "client_internal.h"
-
-static guint expire_source_id;
-
-void
-client_set_expired(struct client *client)
-{
- if (!client_is_expired(client))
- client_schedule_expire();
-
- if (client->source_id != 0) {
- g_source_remove(client->source_id);
- client->source_id = 0;
- }
-
- if (client->channel != NULL) {
- g_io_channel_unref(client->channel);
- client->channel = NULL;
- }
-}
-
-static void
-client_check_expired_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
-{
- struct client *client = data;
-
- if (client_is_expired(client)) {
- g_debug("[%u] expired", client->num);
- client_close(client);
- } else if (!client->idle_waiting && /* idle clients
- never expire */
- (int)g_timer_elapsed(client->last_activity, NULL) >
- client_timeout) {
- g_debug("[%u] timeout", client->num);
- client_close(client);
- }
-}
-
-static void
-client_manager_expire(void)
-{
- client_list_foreach(client_check_expired_callback, NULL);
-}
-
-/**
- * An idle event which calls client_manager_expire().
- */
-static gboolean
-client_manager_expire_event(G_GNUC_UNUSED gpointer data)
-{
- expire_source_id = 0;
- client_manager_expire();
- return false;
-}
-
-void
-client_schedule_expire(void)
-{
- if (expire_source_id == 0)
- /* delayed deletion */
- expire_source_id = g_idle_add(client_manager_expire_event,
- NULL);
-}
-
-void
-client_deinit_expire(void)
-{
- if (expire_source_id != 0)
- g_source_remove(expire_source_id);
-}
diff --git a/src/client_file.c b/src/client_file.c
deleted file mode 100644
index 2ee433308..000000000
--- a/src/client_file.c
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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 "client_file.h"
-#include "client.h"
-#include "ack.h"
-
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <errno.h>
-#include <unistd.h>
-
-bool
-client_allow_file(const struct client *client, const char *path_fs,
- GError **error_r)
-{
-#ifdef WIN32
- (void)client;
- (void)path_fs;
-
- g_set_error(error_r, ack_quark(), ACK_ERROR_PERMISSION,
- "Access denied");
- return false;
-#else
- const int uid = client_get_uid(client);
- if (uid >= 0 && (uid_t)uid == geteuid())
- /* always allow access if user runs his own MPD
- instance */
- return true;
-
- if (uid <= 0) {
- /* unauthenticated client */
- g_set_error(error_r, ack_quark(), ACK_ERROR_PERMISSION,
- "Access denied");
- return false;
- }
-
- struct stat st;
- if (stat(path_fs, &st) < 0) {
- g_set_error(error_r, g_file_error_quark(), errno,
- "%s", g_strerror(errno));
- return false;
- }
-
- if (st.st_uid != (uid_t)uid && (st.st_mode & 0444) != 0444) {
- /* client is not owner */
- g_set_error(error_r, ack_quark(), ACK_ERROR_PERMISSION,
- "Access denied");
- return false;
- }
-
- return true;
-#endif
-}
diff --git a/src/client_file.h b/src/client_file.h
deleted file mode 100644
index bc64bd041..000000000
--- a/src/client_file.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef MPD_CLIENT_FILE_H
-#define MPD_CLIENT_FILE_H
-
-#include <glib.h>
-#include <stdbool.h>
-
-struct client;
-
-/**
- * Is this client allowed to use the specified local file?
- *
- * Note that this function is vulnerable to timing/symlink attacks.
- * We cannot fix this as long as there are plugins that open a file by
- * its name, and not by file descriptor / callbacks.
- *
- * @param path_fs the absolute path name in filesystem encoding
- * @return true if access is allowed
- */
-bool
-client_allow_file(const struct client *client, const char *path_fs,
- GError **error_r);
-
-#endif
diff --git a/src/client_global.c b/src/client_global.c
deleted file mode 100644
index adf3b2f9e..000000000
--- a/src/client_global.c
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "client_internal.h"
-#include "conf.h"
-
-#include <assert.h>
-
-#define CLIENT_TIMEOUT_DEFAULT (60)
-#define CLIENT_MAX_CONNECTIONS_DEFAULT (10)
-#define CLIENT_MAX_COMMAND_LIST_DEFAULT (2048*1024)
-#define CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT (8192*1024)
-
-/* set this to zero to indicate we have no possible clients */
-unsigned int client_max_connections;
-int client_timeout;
-size_t client_max_command_list_size;
-size_t client_max_output_buffer_size;
-
-void client_manager_init(void)
-{
- client_timeout = config_get_positive(CONF_CONN_TIMEOUT,
- CLIENT_TIMEOUT_DEFAULT);
- client_max_connections =
- config_get_positive(CONF_MAX_CONN,
- CLIENT_MAX_CONNECTIONS_DEFAULT);
- client_max_command_list_size =
- config_get_positive(CONF_MAX_COMMAND_LIST_SIZE,
- CLIENT_MAX_COMMAND_LIST_DEFAULT / 1024)
- * 1024;
-
- client_max_output_buffer_size =
- config_get_positive(CONF_MAX_OUTPUT_BUFFER_SIZE,
- CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT / 1024)
- * 1024;
-}
-
-static void client_close_all(void)
-{
- while (!client_list_is_empty()) {
- struct client *client = client_list_get_first();
-
- client_close(client);
- }
-
- assert(client_list_is_empty());
-}
-
-void client_manager_deinit(void)
-{
- client_close_all();
-
- client_max_connections = 0;
-
- client_deinit_expire();
-}
diff --git a/src/client_idle.c b/src/client_idle.c
deleted file mode 100644
index 930911d6e..000000000
--- a/src/client_idle.c
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "client_idle.h"
-#include "client_internal.h"
-#include "idle.h"
-
-#include <assert.h>
-
-/**
- * Send "idle" response to this client.
- */
-static void
-client_idle_notify(struct client *client)
-{
- unsigned flags, i;
- const char *const* idle_names;
-
- assert(client->idle_waiting);
- assert(client->idle_flags != 0);
-
- flags = client->idle_flags;
- client->idle_flags = 0;
- client->idle_waiting = false;
-
- idle_names = idle_get_names();
- for (i = 0; idle_names[i]; ++i) {
- if (flags & (1 << i) & client->idle_subscriptions)
- client_printf(client, "changed: %s\n",
- idle_names[i]);
- }
-
- client_puts(client, "OK\n");
- g_timer_start(client->last_activity);
-}
-
-void
-client_idle_add(struct client *client, unsigned flags)
-{
- if (client_is_expired(client))
- return;
-
- client->idle_flags |= flags;
- if (client->idle_waiting
- && (client->idle_flags & client->idle_subscriptions)) {
- client_idle_notify(client);
- client_write_output(client);
- }
-}
-
-static void
-client_idle_callback(gpointer data, gpointer user_data)
-{
- struct client *client = data;
- unsigned flags = GPOINTER_TO_UINT(user_data);
-
- client_idle_add(client, flags);
-}
-
-void client_manager_idle_add(unsigned flags)
-{
- assert(flags != 0);
-
- client_list_foreach(client_idle_callback, GUINT_TO_POINTER(flags));
-}
-
-bool client_idle_wait(struct client *client, unsigned flags)
-{
- assert(!client->idle_waiting);
-
- client->idle_waiting = true;
- client->idle_subscriptions = flags;
-
- if (client->idle_flags & client->idle_subscriptions) {
- client_idle_notify(client);
- return true;
- } else
- return false;
-}
diff --git a/src/client_idle.h b/src/client_idle.h
deleted file mode 100644
index c56fd014c..000000000
--- a/src/client_idle.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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_CLIENT_IDLE_H
-#define MPD_CLIENT_IDLE_H
-
-#include <stdbool.h>
-
-struct client;
-
-void
-client_idle_add(struct client *client, unsigned flags);
-
-/**
- * Adds the specified idle flags to all clients and immediately sends
- * notifications to all waiting clients.
- */
-void
-client_manager_idle_add(unsigned flags);
-
-/**
- * Checks whether the client has pending idle flags. If yes, they are
- * sent immediately and "true" is returned". If no, it puts the
- * client into waiting mode and returns false.
- */
-bool
-client_idle_wait(struct client *client, unsigned flags);
-
-#endif
diff --git a/src/client_internal.h b/src/client_internal.h
deleted file mode 100644
index ba97e4b8f..000000000
--- a/src/client_internal.h
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * 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_CLIENT_INTERNAL_H
-#define MPD_CLIENT_INTERNAL_H
-
-#include "client.h"
-#include "client_message.h"
-#include "command.h"
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "client"
-
-enum {
- CLIENT_MAX_SUBSCRIPTIONS = 16,
- CLIENT_MAX_MESSAGES = 64,
-};
-
-struct deferred_buffer {
- size_t size;
- char data[sizeof(long)];
-};
-
-struct client {
- struct player_control *player_control;
-
- GIOChannel *channel;
- guint source_id;
-
- /** the buffer for reading lines from the #channel */
- struct fifo_buffer *input;
-
- unsigned permission;
-
- /** the uid of the client process, or -1 if unknown */
- int uid;
-
- /**
- * How long since the last activity from this client?
- */
- GTimer *last_activity;
-
- GSList *cmd_list; /* for when in list mode */
- int cmd_list_OK; /* print OK after each command execution */
- size_t cmd_list_size; /* mem cmd_list consumes */
- GQueue *deferred_send; /* for output if client is slow */
- size_t deferred_bytes; /* mem deferred_send consumes */
- unsigned int num; /* client number */
-
- char send_buf[16384];
- size_t send_buf_used; /* bytes used this instance */
-
- /** is this client waiting for an "idle" response? */
- bool idle_waiting;
-
- /** idle flags pending on this client, to be sent as soon as
- the client enters "idle" */
- unsigned idle_flags;
-
- /** idle flags that the client wants to receive */
- unsigned idle_subscriptions;
-
- /**
- * A list of channel names this client is subscribed to.
- */
- GSList *subscriptions;
-
- /**
- * The number of subscriptions in #subscriptions. Used to
- * limit the number of subscriptions.
- */
- unsigned num_subscriptions;
-
- /**
- * A list of messages this client has received in reverse
- * order (latest first).
- */
- GSList *messages;
-
- /**
- * The number of messages in #messages.
- */
- unsigned num_messages;
-};
-
-extern unsigned int client_max_connections;
-extern int client_timeout;
-extern size_t client_max_command_list_size;
-extern size_t client_max_output_buffer_size;
-
-bool
-client_list_is_empty(void);
-
-bool
-client_list_is_full(void);
-
-struct client *
-client_list_get_first(void);
-
-void
-client_list_add(struct client *client);
-
-void
-client_list_foreach(GFunc func, gpointer user_data);
-
-void
-client_list_remove(struct client *client);
-
-void
-client_close(struct client *client);
-
-static inline void
-new_cmd_list_ptr(struct client *client, const char *s)
-{
- client->cmd_list = g_slist_prepend(client->cmd_list, g_strdup(s));
-}
-
-static inline void
-free_cmd_list(GSList *list)
-{
- for (GSList *tmp = list; tmp != NULL; tmp = g_slist_next(tmp))
- g_free(tmp->data);
-
- g_slist_free(list);
-}
-
-void
-client_set_expired(struct client *client);
-
-/**
- * Schedule an "expired" check for all clients: permanently delete
- * clients which have been set "expired" with client_set_expired().
- */
-void
-client_schedule_expire(void);
-
-/**
- * Removes a scheduled "expired" check.
- */
-void
-client_deinit_expire(void);
-
-enum command_return
-client_read(struct client *client);
-
-enum command_return
-client_process_line(struct client *client, char *line);
-
-void
-client_write_deferred(struct client *client);
-
-void
-client_write_output(struct client *client);
-
-gboolean
-client_in_event(GIOChannel *source, GIOCondition condition,
- gpointer data);
-
-#endif
diff --git a/src/client_list.c b/src/client_list.c
deleted file mode 100644
index 2c7f37aff..000000000
--- a/src/client_list.c
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "client_internal.h"
-
-#include <assert.h>
-
-static GList *clients;
-static unsigned num_clients;
-
-bool
-client_list_is_empty(void)
-{
- return num_clients == 0;
-}
-
-bool
-client_list_is_full(void)
-{
- return num_clients >= client_max_connections;
-}
-
-struct client *
-client_list_get_first(void)
-{
- assert(clients != NULL);
-
- return clients->data;
-}
-
-void
-client_list_add(struct client *client)
-{
- clients = g_list_prepend(clients, client);
- ++num_clients;
-}
-
-void
-client_list_foreach(GFunc func, gpointer user_data)
-{
- g_list_foreach(clients, func, user_data);
-}
-
-void
-client_list_remove(struct client *client)
-{
- assert(num_clients > 0);
- assert(clients != NULL);
-
- clients = g_list_remove(clients, client);
- --num_clients;
-}
diff --git a/src/client_message.c b/src/client_message.c
deleted file mode 100644
index b681b4e7f..000000000
--- a/src/client_message.c
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * 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.
- */
-
-#include "client_message.h"
-
-#include <assert.h>
-#include <glib.h>
-
-G_GNUC_PURE
-static bool
-valid_channel_char(const char ch)
-{
- return g_ascii_isalnum(ch) ||
- ch == '_' || ch == '-' || ch == '.' || ch == ':';
-}
-
-bool
-client_message_valid_channel_name(const char *name)
-{
- do {
- if (!valid_channel_char(*name))
- return false;
- } while (*++name != 0);
-
- return true;
-}
-
-void
-client_message_init_null(struct client_message *msg)
-{
- assert(msg != NULL);
-
- msg->channel = NULL;
- msg->message = NULL;
-}
-
-void
-client_message_init(struct client_message *msg,
- const char *channel, const char *message)
-{
- assert(msg != NULL);
-
- msg->channel = g_strdup(channel);
- msg->message = g_strdup(message);
-}
-
-void
-client_message_copy(struct client_message *dest,
- const struct client_message *src)
-{
- assert(dest != NULL);
- assert(src != NULL);
- assert(client_message_defined(src));
-
- client_message_init(dest, src->channel, src->message);
-}
-
-struct client_message *
-client_message_dup(const struct client_message *src)
-{
- struct client_message *dest = g_slice_new(struct client_message);
- client_message_copy(dest, src);
- return dest;
-}
-
-void
-client_message_deinit(struct client_message *msg)
-{
- assert(msg != NULL);
-
- g_free(msg->channel);
- g_free(msg->message);
-}
-
-void
-client_message_free(struct client_message *msg)
-{
- client_message_deinit(msg);
- g_slice_free(struct client_message, msg);
-}
diff --git a/src/client_message.h b/src/client_message.h
deleted file mode 100644
index 38c2e7615..000000000
--- a/src/client_message.h
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * 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_CLIENT_MESSAGE_H
-#define MPD_CLIENT_MESSAGE_H
-
-#include <assert.h>
-#include <stdbool.h>
-#include <stddef.h>
-#include <glib.h>
-
-/**
- * A client-to-client message.
- */
-struct client_message {
- char *channel;
-
- char *message;
-};
-
-G_GNUC_PURE
-bool
-client_message_valid_channel_name(const char *name);
-
-G_GNUC_PURE
-static inline bool
-client_message_defined(const struct client_message *msg)
-{
- assert(msg != NULL);
- assert((msg->channel == NULL) == (msg->message == NULL));
-
- return msg->channel != NULL;
-}
-
-void
-client_message_init_null(struct client_message *msg);
-
-void
-client_message_init(struct client_message *msg,
- const char *channel, const char *message);
-
-void
-client_message_copy(struct client_message *dest,
- const struct client_message *src);
-
-G_GNUC_MALLOC
-struct client_message *
-client_message_dup(const struct client_message *src);
-
-void
-client_message_deinit(struct client_message *msg);
-
-void
-client_message_free(struct client_message *msg);
-
-#endif
diff --git a/src/client_new.c b/src/client_new.c
deleted file mode 100644
index cf28c43c5..000000000
--- a/src/client_new.c
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "client_internal.h"
-#include "fd_util.h"
-#include "fifo_buffer.h"
-#include "resolver.h"
-#include "permission.h"
-#include "glib_socket.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>
-#endif
-
-
-#define LOG_LEVEL_SECURE G_LOG_LEVEL_INFO
-
-static const char GREETING[] = "OK MPD " PROTOCOL_VERSION "\n";
-
-void
-client_new(struct player_control *player_control,
- int fd, const struct sockaddr *sa, size_t sa_length, int uid)
-{
- static unsigned int next_client_num;
- struct client *client;
- char *remote;
-
- assert(player_control != NULL);
- assert(fd >= 0);
-
-#ifdef HAVE_LIBWRAP
- if (sa->sa_family != AF_UNIX) {
- char *hostaddr = sockaddr_to_string(sa, sa_length, NULL);
- const char *progname = g_get_prgname();
-
- struct request_info req;
- request_init(&req, RQ_FILE, fd, RQ_DAEMON, progname, 0);
-
- fromhost(&req);
-
- if (!hosts_access(&req)) {
- /* tcp wrappers says no */
- g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE,
- "libwrap refused connection (libwrap=%s) from %s",
- progname, hostaddr);
-
- g_free(hostaddr);
- close_socket(fd);
- return;
- }
-
- g_free(hostaddr);
- }
-#endif /* HAVE_WRAP */
-
- if (client_list_is_full()) {
- g_warning("Max Connections Reached!");
- close_socket(fd);
- return;
- }
-
- client = g_new0(struct client, 1);
- client->player_control = player_control;
-
- client->channel = g_io_channel_new_socket(fd);
- /* GLib is responsible for closing the file descriptor */
- g_io_channel_set_close_on_unref(client->channel, true);
- /* NULL encoding means the stream is binary safe; the MPD
- protocol is UTF-8 only, but we are doing this call anyway
- to prevent GLib from messing around with the stream */
- g_io_channel_set_encoding(client->channel, NULL, NULL);
- /* we prefer to do buffering */
- g_io_channel_set_buffered(client->channel, false);
-
- client->source_id = g_io_add_watch(client->channel,
- G_IO_IN|G_IO_ERR|G_IO_HUP,
- client_in_event, client);
-
- client->input = fifo_buffer_new(4096);
-
- client->permission = getDefaultPermissions();
- client->uid = uid;
-
- client->last_activity = g_timer_new();
-
- client->cmd_list = NULL;
- client->cmd_list_OK = -1;
- client->cmd_list_size = 0;
-
- client->deferred_send = g_queue_new();
- client->deferred_bytes = 0;
- client->num = next_client_num++;
-
- client->send_buf_used = 0;
-
- client->subscriptions = NULL;
- client->messages = NULL;
- client->num_messages = 0;
-
- (void)send(fd, GREETING, sizeof(GREETING) - 1, 0);
-
- client_list_add(client);
-
- remote = sockaddr_to_string(sa, sa_length, NULL);
- g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE,
- "[%u] opened from %s", client->num, remote);
- g_free(remote);
-}
-
-static void
-deferred_buffer_free(gpointer data, G_GNUC_UNUSED gpointer user_data)
-{
- struct deferred_buffer *buffer = data;
- g_free(buffer);
-}
-
-void
-client_close(struct client *client)
-{
- client_list_remove(client);
-
- client_set_expired(client);
-
- g_timer_destroy(client->last_activity);
-
- if (client->cmd_list) {
- free_cmd_list(client->cmd_list);
- client->cmd_list = NULL;
- }
-
- g_queue_foreach(client->deferred_send, deferred_buffer_free, NULL);
- g_queue_free(client->deferred_send);
-
- fifo_buffer_free(client->input);
-
- g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE,
- "[%u] closed", client->num);
- g_free(client);
-}
diff --git a/src/client_process.c b/src/client_process.c
deleted file mode 100644
index 57a8a7824..000000000
--- a/src/client_process.c
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "client_internal.h"
-
-#include <string.h>
-
-#define CLIENT_LIST_MODE_BEGIN "command_list_begin"
-#define CLIENT_LIST_OK_MODE_BEGIN "command_list_ok_begin"
-#define CLIENT_LIST_MODE_END "command_list_end"
-
-static enum command_return
-client_process_command_list(struct client *client, bool list_ok, GSList *list)
-{
- enum command_return ret = COMMAND_RETURN_OK;
- unsigned num = 0;
-
- for (GSList *cur = list; cur != NULL; cur = g_slist_next(cur)) {
- char *cmd = cur->data;
-
- g_debug("command_process_list: process command \"%s\"",
- cmd);
- ret = command_process(client, num++, cmd);
- g_debug("command_process_list: command returned %i", ret);
- if (ret != COMMAND_RETURN_OK || client_is_expired(client))
- break;
- else if (list_ok)
- client_puts(client, "list_OK\n");
- }
-
- return ret;
-}
-
-enum command_return
-client_process_line(struct client *client, char *line)
-{
- enum command_return ret;
-
- if (strcmp(line, "noidle") == 0) {
- if (client->idle_waiting) {
- /* send empty idle response and leave idle mode */
- client->idle_waiting = false;
- command_success(client);
- client_write_output(client);
- }
-
- /* do nothing if the client wasn't idling: the client
- has already received the full idle response from
- client_idle_notify(), which he can now evaluate */
-
- return COMMAND_RETURN_OK;
- } else if (client->idle_waiting) {
- /* during idle mode, clients must not send anything
- except "noidle" */
- g_warning("[%u] command \"%s\" during idle",
- client->num, line);
- return COMMAND_RETURN_CLOSE;
- }
-
- if (client->cmd_list_OK >= 0) {
- if (strcmp(line, CLIENT_LIST_MODE_END) == 0) {
- g_debug("[%u] process command list",
- client->num);
-
- /* for scalability reasons, we have prepended
- each new command; now we have to reverse it
- to restore the correct order */
- client->cmd_list = g_slist_reverse(client->cmd_list);
-
- ret = client_process_command_list(client,
- client->cmd_list_OK,
- client->cmd_list);
- g_debug("[%u] process command "
- "list returned %i", client->num, ret);
-
- if (ret == COMMAND_RETURN_CLOSE ||
- client_is_expired(client))
- return COMMAND_RETURN_CLOSE;
-
- if (ret == COMMAND_RETURN_OK)
- command_success(client);
-
- client_write_output(client);
- free_cmd_list(client->cmd_list);
- client->cmd_list = NULL;
- client->cmd_list_OK = -1;
- } else {
- size_t len = strlen(line) + 1;
- client->cmd_list_size += len;
- if (client->cmd_list_size >
- client_max_command_list_size) {
- g_warning("[%u] command list size (%lu) "
- "is larger than the max (%lu)",
- client->num,
- (unsigned long)client->cmd_list_size,
- (unsigned long)client_max_command_list_size);
- return COMMAND_RETURN_CLOSE;
- }
-
- new_cmd_list_ptr(client, line);
- ret = COMMAND_RETURN_OK;
- }
- } else {
- if (strcmp(line, CLIENT_LIST_MODE_BEGIN) == 0) {
- client->cmd_list_OK = 0;
- ret = COMMAND_RETURN_OK;
- } else if (strcmp(line, CLIENT_LIST_OK_MODE_BEGIN) == 0) {
- client->cmd_list_OK = 1;
- ret = COMMAND_RETURN_OK;
- } else {
- g_debug("[%u] process command \"%s\"",
- client->num, line);
- ret = command_process(client, 0, line);
- g_debug("[%u] command returned %i",
- client->num, ret);
-
- if (ret == COMMAND_RETURN_CLOSE ||
- client_is_expired(client))
- return COMMAND_RETURN_CLOSE;
-
- if (ret == COMMAND_RETURN_OK)
- command_success(client);
-
- client_write_output(client);
- }
- }
-
- return ret;
-}
diff --git a/src/client_read.c b/src/client_read.c
deleted file mode 100644
index 26ade264e..000000000
--- a/src/client_read.c
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "client_internal.h"
-#include "fifo_buffer.h"
-
-#include <assert.h>
-#include <string.h>
-
-static char *
-client_read_line(struct client *client)
-{
- const char *p, *newline;
- size_t length;
- char *line;
-
- p = fifo_buffer_read(client->input, &length);
- if (p == NULL)
- return NULL;
-
- newline = memchr(p, '\n', length);
- if (newline == NULL)
- return NULL;
-
- line = g_strndup(p, newline - p);
- fifo_buffer_consume(client->input, newline - p + 1);
-
- return g_strchomp(line);
-}
-
-static enum command_return
-client_input_received(struct client *client, size_t bytesRead)
-{
- char *line;
-
- fifo_buffer_append(client->input, bytesRead);
-
- /* process all lines */
-
- while ((line = client_read_line(client)) != NULL) {
- enum command_return ret = client_process_line(client, line);
- g_free(line);
-
- if (ret == COMMAND_RETURN_KILL ||
- ret == COMMAND_RETURN_CLOSE)
- return ret;
- if (client_is_expired(client))
- return COMMAND_RETURN_CLOSE;
- }
-
- return COMMAND_RETURN_OK;
-}
-
-enum command_return
-client_read(struct client *client)
-{
- char *p;
- size_t max_length;
- GError *error = NULL;
- GIOStatus status;
- gsize bytes_read;
-
- assert(client != NULL);
- assert(client->channel != NULL);
-
- p = fifo_buffer_write(client->input, &max_length);
- if (p == NULL) {
- g_warning("[%u] buffer overflow", client->num);
- return COMMAND_RETURN_CLOSE;
- }
-
- status = g_io_channel_read_chars(client->channel, p, max_length,
- &bytes_read, &error);
- switch (status) {
- case G_IO_STATUS_NORMAL:
- return client_input_received(client, bytes_read);
-
- case G_IO_STATUS_AGAIN:
- /* try again later, after select() */
- return COMMAND_RETURN_OK;
-
- case G_IO_STATUS_EOF:
- /* peer disconnected */
- return COMMAND_RETURN_CLOSE;
-
- case G_IO_STATUS_ERROR:
- /* I/O error */
- g_warning("failed to read from client %d: %s",
- client->num, error->message);
- g_error_free(error);
- return COMMAND_RETURN_CLOSE;
- }
-
- /* unreachable */
- return COMMAND_RETURN_CLOSE;
-}
diff --git a/src/client_subscribe.c b/src/client_subscribe.c
deleted file mode 100644
index c65a7ed31..000000000
--- a/src/client_subscribe.c
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * 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.
- */
-
-#include "client_subscribe.h"
-#include "client_internal.h"
-#include "client_idle.h"
-#include "idle.h"
-
-#include <string.h>
-
-G_GNUC_PURE
-static GSList *
-client_find_subscription(const struct client *client, const char *channel)
-{
- for (GSList *i = client->subscriptions; i != NULL; i = g_slist_next(i))
- if (strcmp((const char *)i->data, channel) == 0)
- return i;
-
- return NULL;
-}
-
-enum client_subscribe_result
-client_subscribe(struct client *client, const char *channel)
-{
- assert(client != NULL);
- assert(channel != NULL);
-
- if (!client_message_valid_channel_name(channel))
- return CLIENT_SUBSCRIBE_INVALID;
-
- if (client_find_subscription(client, channel) != NULL)
- return CLIENT_SUBSCRIBE_ALREADY;
-
- if (client->num_subscriptions >= CLIENT_MAX_SUBSCRIPTIONS)
- return CLIENT_SUBSCRIBE_FULL;
-
- client->subscriptions = g_slist_prepend(client->subscriptions,
- g_strdup(channel));
- ++client->num_subscriptions;
-
- idle_add(IDLE_SUBSCRIPTION);
-
- return CLIENT_SUBSCRIBE_OK;
-}
-
-bool
-client_unsubscribe(struct client *client, const char *channel)
-{
- GSList *i = client_find_subscription(client, channel);
- if (i == NULL)
- return false;
-
- assert(client->num_subscriptions > 0);
-
- client->subscriptions = g_slist_remove(client->subscriptions, i->data);
- --client->num_subscriptions;
-
- idle_add(IDLE_SUBSCRIPTION);
-
- assert((client->num_subscriptions == 0) ==
- (client->subscriptions == NULL));
-
- return true;
-}
-
-void
-client_unsubscribe_all(struct client *client)
-{
- for (GSList *i = client->subscriptions; i != NULL; i = g_slist_next(i))
- g_free(i->data);
-
- g_slist_free(client->subscriptions);
- client->subscriptions = NULL;
- client->num_subscriptions = 0;
-}
-
-bool
-client_push_message(struct client *client, const struct client_message *msg)
-{
- assert(client != NULL);
- assert(msg != NULL);
- assert(client_message_defined(msg));
-
- if (client->num_messages >= CLIENT_MAX_MESSAGES ||
- client_find_subscription(client, msg->channel) == NULL)
- return false;
-
- if (client->messages == NULL)
- client_idle_add(client, IDLE_MESSAGE);
-
- client->messages = g_slist_prepend(client->messages,
- client_message_dup(msg));
- ++client->num_messages;
-
- return true;
-}
-
-GSList *
-client_read_messages(struct client *client)
-{
- GSList *messages = g_slist_reverse(client->messages);
-
- client->messages = NULL;
- client->num_messages = 0;
-
- return messages;
-}
diff --git a/src/client_subscribe.h b/src/client_subscribe.h
deleted file mode 100644
index 09f864417..000000000
--- a/src/client_subscribe.h
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * 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_CLIENT_SUBSCRIBE_H
-#define MPD_CLIENT_SUBSCRIBE_H
-
-#include <stdbool.h>
-#include <glib.h>
-
-struct client;
-struct client_message;
-
-enum client_subscribe_result {
- /** success */
- CLIENT_SUBSCRIBE_OK,
-
- /** invalid channel name */
- CLIENT_SUBSCRIBE_INVALID,
-
- /** already subscribed to this channel */
- CLIENT_SUBSCRIBE_ALREADY,
-
- /** too many subscriptions */
- CLIENT_SUBSCRIBE_FULL,
-};
-
-enum client_subscribe_result
-client_subscribe(struct client *client, const char *channel);
-
-bool
-client_unsubscribe(struct client *client, const char *channel);
-
-void
-client_unsubscribe_all(struct client *client);
-
-bool
-client_push_message(struct client *client, const struct client_message *msg);
-
-G_GNUC_MALLOC
-GSList *
-client_read_messages(struct client *client);
-
-#endif
diff --git a/src/client_write.c b/src/client_write.c
deleted file mode 100644
index 78cfca8a1..000000000
--- a/src/client_write.c
+++ /dev/null
@@ -1,284 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "client_internal.h"
-
-#include <assert.h>
-#include <string.h>
-#include <stdio.h>
-
-static size_t
-client_write_deferred_buffer(struct client *client,
- const struct deferred_buffer *buffer)
-{
- GError *error = NULL;
- GIOStatus status;
- gsize bytes_written;
-
- assert(client != NULL);
- assert(client->channel != NULL);
- assert(buffer != NULL);
-
- status = g_io_channel_write_chars
- (client->channel, buffer->data, buffer->size,
- &bytes_written, &error);
- switch (status) {
- case G_IO_STATUS_NORMAL:
- return bytes_written;
-
- case G_IO_STATUS_AGAIN:
- return 0;
-
- case G_IO_STATUS_EOF:
- /* client has disconnected */
-
- client_set_expired(client);
- return 0;
-
- case G_IO_STATUS_ERROR:
- /* I/O error */
-
- client_set_expired(client);
- g_warning("failed to flush buffer for %i: %s",
- client->num, error->message);
- g_error_free(error);
- return 0;
- }
-
- /* unreachable */
- return 0;
-}
-
-void
-client_write_deferred(struct client *client)
-{
- size_t ret;
-
- while (!g_queue_is_empty(client->deferred_send)) {
- struct deferred_buffer *buf =
- g_queue_peek_head(client->deferred_send);
-
- assert(buf->size > 0);
- assert(buf->size <= client->deferred_bytes);
-
- ret = client_write_deferred_buffer(client, buf);
- if (ret == 0)
- break;
-
- if (ret < buf->size) {
- assert(client->deferred_bytes >= (size_t)ret);
- client->deferred_bytes -= ret;
- buf->size -= ret;
- memmove(buf->data, buf->data + ret, buf->size);
- break;
- } else {
- size_t decr = sizeof(*buf) -
- sizeof(buf->data) + buf->size;
-
- assert(client->deferred_bytes >= decr);
- client->deferred_bytes -= decr;
- g_free(buf);
- g_queue_pop_head(client->deferred_send);
- }
-
- g_timer_start(client->last_activity);
- }
-
- if (g_queue_is_empty(client->deferred_send)) {
- g_debug("[%u] buffer empty %lu", client->num,
- (unsigned long)client->deferred_bytes);
- assert(client->deferred_bytes == 0);
- }
-}
-
-static void client_defer_output(struct client *client,
- const void *data, size_t length)
-{
- size_t alloc;
- struct deferred_buffer *buf;
-
- assert(length > 0);
-
- alloc = sizeof(*buf) - sizeof(buf->data) + length;
- client->deferred_bytes += alloc;
- if (client->deferred_bytes > client_max_output_buffer_size) {
- g_warning("[%u] output buffer size (%lu) is "
- "larger than the max (%lu)",
- client->num,
- (unsigned long)client->deferred_bytes,
- (unsigned long)client_max_output_buffer_size);
- /* cause client to close */
- client_set_expired(client);
- return;
- }
-
- buf = g_malloc(alloc);
- buf->size = length;
- memcpy(buf->data, data, length);
-
- g_queue_push_tail(client->deferred_send, buf);
-}
-
-static void client_write_direct(struct client *client,
- const char *data, size_t length)
-{
- GError *error = NULL;
- GIOStatus status;
- gsize bytes_written;
-
- assert(client != NULL);
- assert(client->channel != NULL);
- assert(data != NULL);
- assert(length > 0);
- assert(g_queue_is_empty(client->deferred_send));
-
- status = g_io_channel_write_chars(client->channel, data, length,
- &bytes_written, &error);
- switch (status) {
- case G_IO_STATUS_NORMAL:
- case G_IO_STATUS_AGAIN:
- break;
-
- case G_IO_STATUS_EOF:
- /* client has disconnected */
-
- client_set_expired(client);
- return;
-
- case G_IO_STATUS_ERROR:
- /* I/O error */
-
- client_set_expired(client);
- g_warning("failed to write to %i: %s",
- client->num, error->message);
- g_error_free(error);
- return;
- }
-
- if (bytes_written < length)
- client_defer_output(client, data + bytes_written,
- length - bytes_written);
-
- if (!g_queue_is_empty(client->deferred_send))
- g_debug("[%u] buffer created", client->num);
-}
-
-void
-client_write_output(struct client *client)
-{
- if (client_is_expired(client) || !client->send_buf_used)
- return;
-
- if (!g_queue_is_empty(client->deferred_send)) {
- client_defer_output(client, client->send_buf,
- client->send_buf_used);
-
- if (client_is_expired(client))
- return;
-
- /* try to flush the deferred buffers now; the current
- server command may take too long to finish, and
- meanwhile try to feed output to the client,
- otherwise it will time out. One reason why
- deferring is slow might be that currently each
- client_write() allocates a new deferred buffer.
- This should be optimized after MPD 0.14. */
- client_write_deferred(client);
- } else
- client_write_direct(client, client->send_buf,
- client->send_buf_used);
-
- client->send_buf_used = 0;
-}
-
-/**
- * Write a block of data to the client.
- */
-static void client_write(struct client *client, const char *buffer, size_t buflen)
-{
- /* if the client is going to be closed, do nothing */
- if (client_is_expired(client))
- return;
-
- while (buflen > 0 && !client_is_expired(client)) {
- size_t copylen;
-
- assert(client->send_buf_used < sizeof(client->send_buf));
-
- copylen = sizeof(client->send_buf) - client->send_buf_used;
- if (copylen > buflen)
- copylen = buflen;
-
- memcpy(client->send_buf + client->send_buf_used, buffer,
- copylen);
- buflen -= copylen;
- client->send_buf_used += copylen;
- buffer += copylen;
- if (client->send_buf_used >= sizeof(client->send_buf))
- client_write_output(client);
- }
-}
-
-void client_puts(struct client *client, const char *s)
-{
- client_write(client, s, strlen(s));
-}
-
-void client_vprintf(struct client *client, const char *fmt, va_list args)
-{
-#ifndef G_OS_WIN32
- va_list tmp;
- int length;
- char *buffer;
-
- va_copy(tmp, args);
- length = vsnprintf(NULL, 0, fmt, tmp);
- va_end(tmp);
-
- if (length <= 0)
- /* wtf.. */
- return;
-
- buffer = g_malloc(length + 1);
- vsnprintf(buffer, length + 1, fmt, args);
- client_write(client, buffer, length);
- g_free(buffer);
-#else
- /* On mingw32, snprintf() expects a 64 bit integer instead of
- a "long int" for "%li". This is not consistent with our
- expectation, so we're using plain sprintf() here, hoping
- the static buffer is large enough. Sorry for this hack,
- but WIN32 development is so painful, I'm not in the mood to
- do it properly now. */
-
- static char buffer[4096];
- vsprintf(buffer, fmt, args);
- client_write(client, buffer, strlen(buffer));
-#endif
-}
-
-G_GNUC_PRINTF(2, 3) void client_printf(struct client *client, const char *fmt, ...)
-{
- va_list args;
-
- va_start(args, fmt);
- client_vprintf(client, fmt, args);
- va_end(args);
-}
diff --git a/src/clock.h b/src/clock.h
index 4ece35ab1..c98b9a652 100644
--- a/src/clock.h
+++ b/src/clock.h
@@ -20,22 +20,30 @@
#ifndef MPD_CLOCK_H
#define MPD_CLOCK_H
-#include <glib.h>
+#include "gcc.h"
#include <stdint.h>
+#ifdef __cplusplus
+extern "C" {
+#endif
+
/**
* Returns the value of a monotonic clock in milliseconds.
*/
-G_GNUC_PURE
+gcc_pure
unsigned
monotonic_clock_ms(void);
/**
* Returns the value of a monotonic clock in microseconds.
*/
-G_GNUC_PURE
+gcc_pure
uint64_t
monotonic_clock_us(void);
+#ifdef __cplusplus
+}
+#endif
+
#endif
diff --git a/src/cmdline.c b/src/cmdline.c
deleted file mode 100644
index cb7eff36a..000000000
--- a/src/cmdline.c
+++ /dev/null
@@ -1,253 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "cmdline.h"
-#include "path.h"
-#include "log.h"
-#include "conf.h"
-#include "decoder_list.h"
-#include "decoder_plugin.h"
-#include "output_list.h"
-#include "output_plugin.h"
-#include "input_registry.h"
-#include "input_plugin.h"
-#include "playlist_list.h"
-#include "playlist_plugin.h"
-#include "ls.h"
-#include "mpd_error.h"
-#include "glib_compat.h"
-
-#ifdef ENABLE_ENCODER
-#include "encoder_list.h"
-#include "encoder_plugin.h"
-#endif
-
-#ifdef ENABLE_ARCHIVE
-#include "archive_list.h"
-#include "archive_plugin.h"
-#endif
-
-#include <glib.h>
-
-#include <stdio.h>
-#include <stdlib.h>
-
-#ifdef G_OS_WIN32
-#define CONFIG_FILE_LOCATION "\\mpd\\mpd.conf"
-#else /* G_OS_WIN32 */
-#define USER_CONFIG_FILE_LOCATION1 ".mpdconf"
-#define USER_CONFIG_FILE_LOCATION2 ".mpd/mpd.conf"
-#endif
-
-static GQuark
-cmdline_quark(void)
-{
- return g_quark_from_static_string("cmdline");
-}
-
-G_GNUC_NORETURN
-static void version(void)
-{
- puts(PACKAGE " (MPD: Music Player Daemon) " VERSION " \n"
- "\n"
- "Copyright (C) 2003-2007 Warren Dukes <warren.dukes@gmail.com>\n"
- "Copyright (C) 2008-2012 Max Kellermann <max@duempel.org>\n"
- "This is free software; see the source for copying conditions. There is NO\n"
- "warranty; not even MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
- "\n"
- "Decoders plugins:");
-
- decoder_plugins_for_each(plugin) {
- printf(" [%s]", plugin->name);
-
- const char *const*suffixes = plugin->suffixes;
- if (suffixes != NULL)
- for (; *suffixes != NULL; ++suffixes)
- printf(" %s", *suffixes);
-
- puts("");
- }
-
- puts("\n"
- "Output plugins:");
- audio_output_plugins_for_each(plugin)
- printf(" %s", plugin->name);
- puts("");
-
-#ifdef ENABLE_ENCODER
- puts("\n"
- "Encoder plugins:");
- encoder_plugins_for_each(plugin)
- printf(" %s", plugin->name);
- puts("");
-#endif
-
-#ifdef ENABLE_ARCHIVE
- puts("\n"
- "Archive plugins:");
- archive_plugins_for_each(plugin) {
- printf(" [%s]", plugin->name);
-
- const char *const*suffixes = plugin->suffixes;
- if (suffixes != NULL)
- for (; *suffixes != NULL; ++suffixes)
- printf(" %s", *suffixes);
-
- puts("");
- }
-#endif
-
- puts("\n"
- "Input plugins:");
- input_plugins_for_each(plugin)
- printf(" %s", plugin->name);
-
- puts("\n\n"
- "Playlist plugins:");
- playlist_plugins_for_each(plugin)
- printf(" %s", plugin->name);
-
- puts("\n\n"
- "Protocols:");
- print_supported_uri_schemes_to_fp(stdout);
-
- exit(EXIT_SUCCESS);
-}
-
-static const char *summary =
- "Music Player Daemon - a daemon for playing music.";
-
-bool
-parse_cmdline(int argc, char **argv, struct options *options,
- GError **error_r)
-{
- GError *error = NULL;
- 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", NULL },
- { "no-config", 0, 0, G_OPTION_ARG_NONE, &option_no_config,
- "don't read from config", NULL },
- { "no-daemon", 0, 0, G_OPTION_ARG_NONE, &option_no_daemon,
- "don't detach from console", NULL },
- { "stdout", 0, 0, G_OPTION_ARG_NONE, &options->log_stderr,
- NULL, NULL },
- { "stderr", 0, 0, G_OPTION_ARG_NONE, &options->log_stderr,
- "print messages to stderr", NULL },
- { "verbose", 'v', 0, G_OPTION_ARG_NONE, &options->verbose,
- "verbose logging", NULL },
- { "version", 'V', 0, G_OPTION_ARG_NONE, &option_version,
- "print version number", NULL },
- { .long_name = NULL }
- };
-
- 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, NULL);
-
- g_option_context_set_summary(context, summary);
-
- ret = g_option_context_parse(context, &argc, &argv, &error);
- g_option_context_free(context);
-
- if (!ret)
- MPD_ERROR("option parsing failed: %s\n", error->message);
-
- if (option_version)
- version();
-
- /* 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) {
- g_debug("Ignoring config, using daemon defaults\n");
- return true;
- } else if (argc <= 1) {
- /* default configuration file path */
- char *path1;
-
-#ifdef G_OS_WIN32
- path1 = g_build_filename(g_get_user_config_dir(),
- CONFIG_FILE_LOCATION, NULL);
- if (g_file_test(path1, G_FILE_TEST_IS_REGULAR))
- ret = config_read_file(path1, error_r);
- else {
- int i = 0;
- char *system_path = NULL;
- const char * const *system_config_dirs;
-
- system_config_dirs = g_get_system_config_dirs();
-
- while(system_config_dirs[i] != NULL) {
- system_path = g_build_filename(system_config_dirs[i],
- CONFIG_FILE_LOCATION,
- NULL);
- if(g_file_test(system_path,
- G_FILE_TEST_IS_REGULAR)) {
- ret = config_read_file(system_path,error_r);
- g_free(system_path);
- break;
- } else
- g_free(system_path);
- ++i;
- }
- }
-#else /* G_OS_WIN32 */
- char *path2;
- path1 = g_build_filename(g_get_home_dir(),
- USER_CONFIG_FILE_LOCATION1, NULL);
- path2 = g_build_filename(g_get_home_dir(),
- USER_CONFIG_FILE_LOCATION2, NULL);
- if (g_file_test(path1, G_FILE_TEST_IS_REGULAR))
- ret = config_read_file(path1, error_r);
- else if (g_file_test(path2, G_FILE_TEST_IS_REGULAR))
- ret = config_read_file(path2, error_r);
- else if (g_file_test(SYSTEM_CONFIG_FILE_LOCATION,
- G_FILE_TEST_IS_REGULAR))
- ret = config_read_file(SYSTEM_CONFIG_FILE_LOCATION,
- error_r);
-#endif
-
- g_free(path1);
-#ifndef G_OS_WIN32
- g_free(path2);
-#endif
-
- return ret;
- } else if (argc == 2) {
- /* specified configuration file */
- return config_read_file(argv[1], error_r);
- } else {
- g_set_error(error_r, cmdline_quark(), 0,
- "too many arguments");
- return false;
- }
-}
diff --git a/src/cmdline.h b/src/cmdline.h
deleted file mode 100644
index 68f625a6c..000000000
--- a/src/cmdline.h
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * 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 CMDLINE_H
-#define CMDLINE_H
-
-#include <glib.h>
-
-#include <stdbool.h>
-
-struct options {
- gboolean kill;
- gboolean daemon;
- gboolean log_stderr;
- gboolean verbose;
-};
-
-bool
-parse_cmdline(int argc, char **argv, struct options *options,
- GError **error_r);
-
-#endif
diff --git a/src/command.c b/src/command.c
deleted file mode 100644
index b3318c68b..000000000
--- a/src/command.c
+++ /dev/null
@@ -1,2302 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "command.h"
-#include "protocol/argparser.h"
-#include "protocol/result.h"
-#include "player_control.h"
-#include "playlist.h"
-#include "playlist_print.h"
-#include "playlist_save.h"
-#include "playlist_queue.h"
-#include "playlist_error.h"
-#include "queue_print.h"
-#include "ls.h"
-#include "uri.h"
-#include "decoder_print.h"
-#include "directory.h"
-#include "database.h"
-#include "update.h"
-#include "volume.h"
-#include "stats.h"
-#include "permission.h"
-#include "tokenizer.h"
-#include "stored_playlist.h"
-#include "ack.h"
-#include "output_command.h"
-#include "output_print.h"
-#include "locate.h"
-#include "dbUtils.h"
-#include "db_error.h"
-#include "db_print.h"
-#include "db_selection.h"
-#include "db_lock.h"
-#include "tag.h"
-#include "client.h"
-#include "client_idle.h"
-#include "client_internal.h"
-#include "client_subscribe.h"
-#include "client_file.h"
-#include "tag_print.h"
-#include "path.h"
-#include "replay_gain_config.h"
-#include "idle.h"
-#include "mapper.h"
-#include "song.h"
-#include "song_print.h"
-
-#ifdef ENABLE_SQLITE
-#include "sticker.h"
-#include "sticker_print.h"
-#include "song_sticker.h"
-#endif
-
-#include <assert.h>
-#include <time.h>
-#include <stdlib.h>
-#include <errno.h>
-
-#define COMMAND_STATUS_STATE "state"
-#define COMMAND_STATUS_REPEAT "repeat"
-#define COMMAND_STATUS_SINGLE "single"
-#define COMMAND_STATUS_CONSUME "consume"
-#define COMMAND_STATUS_RANDOM "random"
-#define COMMAND_STATUS_PLAYLIST "playlist"
-#define COMMAND_STATUS_PLAYLIST_LENGTH "playlistlength"
-#define COMMAND_STATUS_SONG "song"
-#define COMMAND_STATUS_SONGID "songid"
-#define COMMAND_STATUS_NEXTSONG "nextsong"
-#define COMMAND_STATUS_NEXTSONGID "nextsongid"
-#define COMMAND_STATUS_TIME "time"
-#define COMMAND_STATUS_BITRATE "bitrate"
-#define COMMAND_STATUS_ERROR "error"
-#define COMMAND_STATUS_CROSSFADE "xfade"
-#define COMMAND_STATUS_MIXRAMPDB "mixrampdb"
-#define COMMAND_STATUS_MIXRAMPDELAY "mixrampdelay"
-#define COMMAND_STATUS_AUDIO "audio"
-#define COMMAND_STATUS_UPDATING_DB "updating_db"
-
-/*
- * The most we ever use is for search/find, and that limits it to the
- * number of tags we can have. Add one for the command, and one extra
- * to catch errors clients may send us
- */
-#define COMMAND_ARGV_MAX (2+(TAG_NUM_OF_ITEM_TYPES*2))
-
-/* if min: -1 don't check args *
- * if max: -1 no max args */
-struct command {
- const char *cmd;
- unsigned permission;
- int min;
- int max;
- enum command_return (*handler)(struct client *client, int argc, char **argv);
-};
-
-static enum command_return
-print_playlist_result(struct client *client,
- enum playlist_result result)
-{
- switch (result) {
- case PLAYLIST_RESULT_SUCCESS:
- return COMMAND_RETURN_OK;
-
- case PLAYLIST_RESULT_ERRNO:
- command_error(client, ACK_ERROR_SYSTEM, "%s",
- g_strerror(errno));
- return COMMAND_RETURN_ERROR;
-
- case PLAYLIST_RESULT_DENIED:
- command_error(client, ACK_ERROR_PERMISSION, "Access denied");
- return COMMAND_RETURN_ERROR;
-
- case PLAYLIST_RESULT_NO_SUCH_SONG:
- command_error(client, ACK_ERROR_NO_EXIST, "No such song");
- return COMMAND_RETURN_ERROR;
-
- case PLAYLIST_RESULT_NO_SUCH_LIST:
- command_error(client, ACK_ERROR_NO_EXIST, "No such playlist");
- return COMMAND_RETURN_ERROR;
-
- case PLAYLIST_RESULT_LIST_EXISTS:
- command_error(client, ACK_ERROR_EXIST,
- "Playlist already exists");
- return COMMAND_RETURN_ERROR;
-
- case PLAYLIST_RESULT_BAD_NAME:
- command_error(client, ACK_ERROR_ARG,
- "playlist name is invalid: "
- "playlist names may not contain slashes,"
- " newlines or carriage returns");
- return COMMAND_RETURN_ERROR;
-
- case PLAYLIST_RESULT_BAD_RANGE:
- command_error(client, ACK_ERROR_ARG, "Bad song index");
- return COMMAND_RETURN_ERROR;
-
- case PLAYLIST_RESULT_NOT_PLAYING:
- command_error(client, ACK_ERROR_PLAYER_SYNC, "Not playing");
- return COMMAND_RETURN_ERROR;
-
- case PLAYLIST_RESULT_TOO_LARGE:
- command_error(client, ACK_ERROR_PLAYLIST_MAX,
- "playlist is at the max size");
- return COMMAND_RETURN_ERROR;
-
- case PLAYLIST_RESULT_DISABLED:
- command_error(client, ACK_ERROR_UNKNOWN,
- "stored playlist support is disabled");
- return COMMAND_RETURN_ERROR;
- }
-
- assert(0);
- return COMMAND_RETURN_ERROR;
-}
-
-/**
- * Send the GError to the client and free the GError.
- */
-static enum command_return
-print_error(struct client *client, GError *error)
-{
- assert(client != NULL);
- assert(error != NULL);
-
- g_warning("%s", error->message);
-
- if (error->domain == playlist_quark()) {
- enum playlist_result result = error->code;
- g_error_free(error);
- return print_playlist_result(client, result);
- } else if (error->domain == ack_quark()) {
- command_error(client, error->code, "%s", error->message);
- g_error_free(error);
- return COMMAND_RETURN_ERROR;
- } else if (error->domain == db_quark()) {
- switch ((enum db_error)error->code) {
- case DB_DISABLED:
- command_error(client, ACK_ERROR_NO_EXIST, "%s",
- error->message);
- g_error_free(error);
- return COMMAND_RETURN_ERROR;
-
- case DB_NOT_FOUND:
- g_error_free(error);
- command_error(client, ACK_ERROR_NO_EXIST, "Not found");
- return COMMAND_RETURN_ERROR;
- }
- } else if (error->domain == g_file_error_quark()) {
- command_error(client, ACK_ERROR_SYSTEM, "%s",
- g_strerror(error->code));
- g_error_free(error);
- return COMMAND_RETURN_ERROR;
- }
-
- g_error_free(error);
- command_error(client, ACK_ERROR_UNKNOWN, "error");
- return COMMAND_RETURN_ERROR;
-}
-
-static void
-print_spl_list(struct client *client, GPtrArray *list)
-{
- for (unsigned i = 0; i < list->len; ++i) {
- struct stored_playlist_info *playlist =
- g_ptr_array_index(list, i);
- time_t t;
-#ifndef WIN32
- struct tm tm;
-#endif
- char timestamp[32];
-
- client_printf(client, "playlist: %s\n", playlist->name);
-
- t = playlist->mtime;
- strftime(timestamp, sizeof(timestamp),
-#ifdef G_OS_WIN32
- "%Y-%m-%dT%H:%M:%SZ",
- gmtime(&t)
-#else
- "%FT%TZ",
- gmtime_r(&t, &tm)
-#endif
- );
- client_printf(client, "Last-Modified: %s\n", timestamp);
- }
-}
-
-static enum command_return
-handle_urlhandlers(struct client *client,
- G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
-{
- if (client_is_local(client))
- client_puts(client, "handler: file://\n");
- print_supported_uri_schemes(client);
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
-handle_decoders(struct client *client,
- G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
-{
- decoder_list_print(client);
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
-handle_tagtypes(struct client *client,
- G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
-{
- tag_print_types(client);
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
-handle_play(struct client *client, int argc, char *argv[])
-{
- int song = -1;
- enum playlist_result result;
-
- if (argc == 2 && !check_int(client, &song, argv[1]))
- return COMMAND_RETURN_ERROR;
- result = playlist_play(&g_playlist, client->player_control, song);
- return print_playlist_result(client, result);
-}
-
-static enum command_return
-handle_playid(struct client *client, int argc, char *argv[])
-{
- int id = -1;
- enum playlist_result result;
-
- if (argc == 2 && !check_int(client, &id, argv[1]))
- return COMMAND_RETURN_ERROR;
-
- result = playlist_play_id(&g_playlist, client->player_control, id);
- return print_playlist_result(client, result);
-}
-
-static enum command_return
-handle_stop(G_GNUC_UNUSED struct client *client,
- G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
-{
- playlist_stop(&g_playlist, client->player_control);
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
-handle_currentsong(struct client *client,
- G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
-{
- playlist_print_current(client, &g_playlist);
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
-handle_pause(struct client *client,
- int argc, char *argv[])
-{
- if (argc == 2) {
- bool pause_flag;
- if (!check_bool(client, &pause_flag, argv[1]))
- return COMMAND_RETURN_ERROR;
-
- pc_set_pause(client->player_control, pause_flag);
- } else
- pc_pause(client->player_control);
-
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
-handle_status(struct client *client,
- G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
-{
- const char *state = NULL;
- struct player_status player_status;
- int updateJobId;
- char *error;
- int song;
-
- pc_get_status(client->player_control, &player_status);
-
- switch (player_status.state) {
- case PLAYER_STATE_STOP:
- state = "stop";
- break;
- case PLAYER_STATE_PAUSE:
- state = "pause";
- break;
- case PLAYER_STATE_PLAY:
- state = "play";
- break;
- }
-
- client_printf(client,
- "volume: %i\n"
- COMMAND_STATUS_REPEAT ": %i\n"
- COMMAND_STATUS_RANDOM ": %i\n"
- COMMAND_STATUS_SINGLE ": %i\n"
- COMMAND_STATUS_CONSUME ": %i\n"
- COMMAND_STATUS_PLAYLIST ": %li\n"
- COMMAND_STATUS_PLAYLIST_LENGTH ": %i\n"
- COMMAND_STATUS_CROSSFADE ": %i\n"
- COMMAND_STATUS_MIXRAMPDB ": %f\n"
- COMMAND_STATUS_MIXRAMPDELAY ": %f\n"
- COMMAND_STATUS_STATE ": %s\n",
- volume_level_get(),
- playlist_get_repeat(&g_playlist),
- playlist_get_random(&g_playlist),
- playlist_get_single(&g_playlist),
- playlist_get_consume(&g_playlist),
- playlist_get_version(&g_playlist),
- playlist_get_length(&g_playlist),
- (int)(pc_get_cross_fade(client->player_control) + 0.5),
- pc_get_mixramp_db(client->player_control),
- pc_get_mixramp_delay(client->player_control),
- state);
-
- song = playlist_get_current_song(&g_playlist);
- if (song >= 0) {
- client_printf(client,
- COMMAND_STATUS_SONG ": %i\n"
- COMMAND_STATUS_SONGID ": %u\n",
- song, playlist_get_song_id(&g_playlist, song));
- }
-
- if (player_status.state != PLAYER_STATE_STOP) {
- client_printf(client,
- COMMAND_STATUS_TIME ": %i:%i\n"
- "elapsed: %1.3f\n"
- COMMAND_STATUS_BITRATE ": %u\n",
- (int)(player_status.elapsed_time + 0.5),
- (int)(player_status.total_time + 0.5),
- player_status.elapsed_time,
- player_status.bit_rate);
-
- if (audio_format_defined(&player_status.audio_format)) {
- struct audio_format_string af_string;
-
- client_printf(client,
- COMMAND_STATUS_AUDIO ": %s\n",
- audio_format_to_string(&player_status.audio_format,
- &af_string));
- }
- }
-
- if ((updateJobId = isUpdatingDB())) {
- client_printf(client,
- COMMAND_STATUS_UPDATING_DB ": %i\n",
- updateJobId);
- }
-
- error = pc_get_error_message(client->player_control);
- if (error != NULL) {
- client_printf(client,
- COMMAND_STATUS_ERROR ": %s\n",
- error);
- g_free(error);
- }
-
- song = playlist_get_next_song(&g_playlist);
- if (song >= 0) {
- client_printf(client,
- COMMAND_STATUS_NEXTSONG ": %i\n"
- COMMAND_STATUS_NEXTSONGID ": %u\n",
- song, playlist_get_song_id(&g_playlist, song));
- }
-
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
-handle_kill(G_GNUC_UNUSED struct client *client,
- G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
-{
- return COMMAND_RETURN_KILL;
-}
-
-static enum command_return
-handle_close(G_GNUC_UNUSED struct client *client,
- G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
-{
- return COMMAND_RETURN_CLOSE;
-}
-
-static enum command_return
-handle_add(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
-{
- char *uri = argv[1];
- enum playlist_result result;
-
- if (strncmp(uri, "file:///", 8) == 0) {
- const char *path = uri + 7;
-
- GError *error = NULL;
- if (!client_allow_file(client, path, &error))
- return print_error(client, error);
-
- result = playlist_append_file(&g_playlist,
- client->player_control,
- path,
- NULL);
- return print_playlist_result(client, result);
- }
-
- if (uri_has_scheme(uri)) {
- if (!uri_supported_scheme(uri)) {
- command_error(client, ACK_ERROR_NO_EXIST,
- "unsupported URI scheme");
- return COMMAND_RETURN_ERROR;
- }
-
- result = playlist_append_uri(&g_playlist,
- client->player_control,
- uri, NULL);
- return print_playlist_result(client, result);
- }
-
- GError *error = NULL;
- return addAllIn(client->player_control, uri, &error)
- ? COMMAND_RETURN_OK
- : print_error(client, error);
-}
-
-static enum command_return
-handle_addid(struct client *client, int argc, char *argv[])
-{
- char *uri = argv[1];
- unsigned added_id;
- enum playlist_result result;
-
- if (strncmp(uri, "file:///", 8) == 0) {
- const char *path = uri + 7;
-
- GError *error = NULL;
- if (!client_allow_file(client, path, &error))
- return print_error(client, error);
-
- result = playlist_append_file(&g_playlist,
- client->player_control,
- path,
- &added_id);
- } else {
- if (uri_has_scheme(uri) && !uri_supported_scheme(uri)) {
- command_error(client, ACK_ERROR_NO_EXIST,
- "unsupported URI scheme");
- return COMMAND_RETURN_ERROR;
- }
-
- result = playlist_append_uri(&g_playlist,
- client->player_control,
- uri, &added_id);
- }
-
- if (result != PLAYLIST_RESULT_SUCCESS)
- return print_playlist_result(client, result);
-
- if (argc == 3) {
- unsigned to;
- if (!check_unsigned(client, &to, argv[2]))
- return COMMAND_RETURN_ERROR;
- result = playlist_move_id(&g_playlist, client->player_control,
- added_id, to);
- if (result != PLAYLIST_RESULT_SUCCESS) {
- enum command_return ret =
- print_playlist_result(client, result);
- playlist_delete_id(&g_playlist, client->player_control,
- added_id);
- return ret;
- }
- }
-
- client_printf(client, "Id: %u\n", added_id);
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
-handle_delete(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
-{
- unsigned start, end;
- enum playlist_result result;
-
- if (!check_range(client, &start, &end, argv[1]))
- return COMMAND_RETURN_ERROR;
-
- result = playlist_delete_range(&g_playlist, client->player_control,
- start, end);
- return print_playlist_result(client, result);
-}
-
-static enum command_return
-handle_deleteid(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
-{
- unsigned id;
- enum playlist_result result;
-
- if (!check_unsigned(client, &id, argv[1]))
- return COMMAND_RETURN_ERROR;
-
- result = playlist_delete_id(&g_playlist, client->player_control, id);
- return print_playlist_result(client, result);
-}
-
-static enum command_return
-handle_playlist(struct client *client,
- G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
-{
- playlist_print_uris(client, &g_playlist);
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
-handle_shuffle(G_GNUC_UNUSED struct client *client,
- G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
-{
- unsigned start = 0, end = queue_length(&g_playlist.queue);
- if (argc == 2 && !check_range(client, &start, &end, argv[1]))
- return COMMAND_RETURN_ERROR;
-
- playlist_shuffle(&g_playlist, client->player_control, start, end);
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
-handle_clear(G_GNUC_UNUSED struct client *client,
- G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
-{
- playlist_clear(&g_playlist, client->player_control);
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
-handle_save(struct client *client,
- G_GNUC_UNUSED int argc, char *argv[])
-{
- enum playlist_result result;
-
- result = spl_save_playlist(argv[1], &g_playlist);
- return print_playlist_result(client, result);
-}
-
-static enum command_return
-handle_load(struct client *client, int argc, char *argv[])
-{
- unsigned start_index, end_index;
-
- if (argc < 3) {
- start_index = 0;
- end_index = G_MAXUINT;
- } else if (!check_range(client, &start_index, &end_index, argv[2]))
- return COMMAND_RETURN_ERROR;
-
- enum playlist_result result;
-
- result = playlist_open_into_queue(argv[1],
- start_index, end_index,
- &g_playlist,
- client->player_control, true);
- if (result != PLAYLIST_RESULT_NO_SUCH_LIST)
- return print_playlist_result(client, result);
-
- GError *error = NULL;
- if (playlist_load_spl(&g_playlist, client->player_control,
- argv[1], start_index, end_index,
- &error))
- return COMMAND_RETURN_OK;
-
- if (error->domain == playlist_quark() &&
- error->code == PLAYLIST_RESULT_BAD_NAME)
- /* the message for BAD_NAME is confusing when the
- client wants to load a playlist file from the music
- directory; patch the GError object to show "no such
- playlist" instead */
- error->code = PLAYLIST_RESULT_NO_SUCH_LIST;
-
- return print_error(client, error);
-}
-
-static enum command_return
-handle_listplaylist(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
-{
- if (playlist_file_print(client, argv[1], false))
- return COMMAND_RETURN_OK;
-
- GError *error = NULL;
- return spl_print(client, argv[1], false, &error)
- ? COMMAND_RETURN_OK
- : print_error(client, error);
-}
-
-static enum command_return
-handle_listplaylistinfo(struct client *client,
- G_GNUC_UNUSED int argc, char *argv[])
-{
- if (playlist_file_print(client, argv[1], true))
- return COMMAND_RETURN_OK;
-
- GError *error = NULL;
- return spl_print(client, argv[1], true, &error)
- ? COMMAND_RETURN_OK
- : print_error(client, error);
-}
-
-static enum command_return
-handle_lsinfo(struct client *client, int argc, char *argv[])
-{
- const char *uri;
-
- if (argc == 2)
- uri = argv[1];
- else
- /* default is root directory */
- uri = "";
-
- if (strncmp(uri, "file:///", 8) == 0) {
- /* print information about an arbitrary local file */
- const char *path = uri + 7;
-
- GError *error = NULL;
- if (!client_allow_file(client, path, &error))
- return print_error(client, error);
-
- struct song *song = song_file_load(path, NULL);
- if (song == NULL) {
- command_error(client, ACK_ERROR_NO_EXIST,
- "No such file");
- return COMMAND_RETURN_ERROR;
- }
-
- song_print_info(client, song);
- song_free(song);
- return COMMAND_RETURN_OK;
- }
-
- struct db_selection selection;
- db_selection_init(&selection, uri, false);
-
- GError *error = NULL;
- if (!db_selection_print(client, &selection, true, &error))
- return print_error(client, error);
-
- if (isRootDirectory(uri)) {
- GPtrArray *list = spl_list(NULL);
- if (list != NULL) {
- print_spl_list(client, list);
- spl_list_free(list);
- }
- }
-
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
-handle_rm(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
-{
- GError *error = NULL;
- return spl_delete(argv[1], &error)
- ? COMMAND_RETURN_OK
- : print_error(client, error);
-}
-
-static enum command_return
-handle_rename(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
-{
- GError *error = NULL;
- return spl_rename(argv[1], argv[2], &error)
- ? COMMAND_RETURN_OK
- : print_error(client, error);
-}
-
-static enum command_return
-handle_plchanges(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
-{
- uint32_t version;
-
- if (!check_uint32(client, &version, argv[1]))
- return COMMAND_RETURN_ERROR;
-
- playlist_print_changes_info(client, &g_playlist, version);
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
-handle_plchangesposid(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
-{
- uint32_t version;
-
- if (!check_uint32(client, &version, argv[1]))
- return COMMAND_RETURN_ERROR;
-
- playlist_print_changes_position(client, &g_playlist, version);
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
-handle_playlistinfo(struct client *client, int argc, char *argv[])
-{
- unsigned start = 0, end = G_MAXUINT;
- bool ret;
-
- if (argc == 2 && !check_range(client, &start, &end, argv[1]))
- return COMMAND_RETURN_ERROR;
-
- ret = playlist_print_info(client, &g_playlist, start, end);
- if (!ret)
- return print_playlist_result(client,
- PLAYLIST_RESULT_BAD_RANGE);
-
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
-handle_playlistid(struct client *client, int argc, char *argv[])
-{
- if (argc >= 2) {
- unsigned id;
- if (!check_unsigned(client, &id, argv[1]))
- return COMMAND_RETURN_ERROR;
-
- bool ret = playlist_print_id(client, &g_playlist, id);
- if (!ret)
- return print_playlist_result(client,
- PLAYLIST_RESULT_NO_SUCH_SONG);
- } else {
- playlist_print_info(client, &g_playlist, 0, G_MAXUINT);
- }
-
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
-handle_find(struct client *client, int argc, char *argv[])
-{
- struct locate_item_list *list =
- locate_item_list_parse(argv + 1, argc - 1);
-
- if (list == NULL || list->length == 0) {
- if (list != NULL)
- locate_item_list_free(list);
-
- command_error(client, ACK_ERROR_ARG, "incorrect arguments");
- return COMMAND_RETURN_ERROR;
- }
-
- GError *error = NULL;
- enum command_return ret = findSongsIn(client, "", list, &error)
- ? COMMAND_RETURN_OK
- : print_error(client, error);
-
- locate_item_list_free(list);
-
- return ret;
-}
-
-static enum command_return
-handle_findadd(struct client *client, int argc, char *argv[])
-{
- struct locate_item_list *list =
- locate_item_list_parse(argv + 1, argc - 1);
- if (list == NULL || list->length == 0) {
- if (list != NULL)
- locate_item_list_free(list);
-
- command_error(client, ACK_ERROR_ARG, "incorrect arguments");
- return COMMAND_RETURN_ERROR;
- }
-
- GError *error = NULL;
- enum command_return ret =
- findAddIn(client->player_control, "", list, &error)
- ? COMMAND_RETURN_OK
- : print_error(client, error);
-
- locate_item_list_free(list);
-
- return ret;
-}
-
-static enum command_return
-handle_search(struct client *client, int argc, char *argv[])
-{
- struct locate_item_list *list =
- locate_item_list_parse(argv + 1, argc - 1);
-
- if (list == NULL || list->length == 0) {
- if (list != NULL)
- locate_item_list_free(list);
-
- command_error(client, ACK_ERROR_ARG, "incorrect arguments");
- return COMMAND_RETURN_ERROR;
- }
-
- GError *error = NULL;
- enum command_return ret = searchForSongsIn(client, "", list, &error)
- ? COMMAND_RETURN_OK
- : print_error(client, error);
-
- locate_item_list_free(list);
-
- return ret;
-}
-
-static enum command_return
-handle_searchadd(struct client *client, int argc, char *argv[])
-{
- struct locate_item_list *list =
- locate_item_list_parse(argv + 1, argc - 1);
-
- if (list == NULL || list->length == 0) {
- if (list != NULL)
- locate_item_list_free(list);
-
- command_error(client, ACK_ERROR_ARG, "incorrect arguments");
- return COMMAND_RETURN_ERROR;
- }
-
- GError *error = NULL;
- enum command_return ret = search_add_songs(client->player_control,
- "", list, &error)
- ? COMMAND_RETURN_OK
- : print_error(client, error);
-
- locate_item_list_free(list);
-
- return ret;
-}
-
-static enum command_return
-handle_searchaddpl(struct client *client, int argc, char *argv[])
-{
- const char *playlist = argv[1];
-
- struct locate_item_list *list =
- locate_item_list_parse(argv + 2, argc - 2);
-
- if (list == NULL || list->length == 0) {
- if (list != NULL)
- locate_item_list_free(list);
-
- command_error(client, ACK_ERROR_ARG, "incorrect arguments");
- return COMMAND_RETURN_ERROR;
- }
-
- GError *error = NULL;
- enum command_return ret =
- search_add_to_playlist("", playlist, list, &error)
- ? COMMAND_RETURN_OK
- : print_error(client, error);
-
- locate_item_list_free(list);
-
- return ret;
-}
-
-static enum command_return
-handle_count(struct client *client, int argc, char *argv[])
-{
- struct locate_item_list *list =
- locate_item_list_parse(argv + 1, argc - 1);
-
- if (list == NULL || list->length == 0) {
- if (list != NULL)
- locate_item_list_free(list);
-
- command_error(client, ACK_ERROR_ARG, "incorrect arguments");
- return COMMAND_RETURN_ERROR;
- }
-
- GError *error = NULL;
- enum command_return ret =
- searchStatsForSongsIn(client, "", list, &error)
- ? COMMAND_RETURN_OK
- : print_error(client, error);
-
- locate_item_list_free(list);
-
- return ret;
-}
-
-static enum command_return
-handle_playlistfind(struct client *client, int argc, char *argv[])
-{
- struct locate_item_list *list =
- locate_item_list_parse(argv + 1, argc - 1);
-
- if (list == NULL || list->length == 0) {
- if (list != NULL)
- locate_item_list_free(list);
-
- command_error(client, ACK_ERROR_ARG, "incorrect arguments");
- return COMMAND_RETURN_ERROR;
- }
-
- playlist_print_find(client, &g_playlist, list);
-
- locate_item_list_free(list);
-
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
-handle_playlistsearch(struct client *client, int argc, char *argv[])
-{
- struct locate_item_list *list =
- locate_item_list_parse(argv + 1, argc - 1);
-
- if (list == NULL || list->length == 0) {
- if (list != NULL)
- locate_item_list_free(list);
-
- command_error(client, ACK_ERROR_ARG, "incorrect arguments");
- return COMMAND_RETURN_ERROR;
- }
-
- playlist_print_search(client, &g_playlist, list);
-
- locate_item_list_free(list);
-
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
-handle_playlistdelete(struct client *client,
- G_GNUC_UNUSED int argc, char *argv[]) {
- char *playlist = argv[1];
- unsigned from;
-
- if (!check_unsigned(client, &from, argv[2]))
- return COMMAND_RETURN_ERROR;
-
- GError *error = NULL;
- return spl_remove_index(playlist, from, &error)
- ? COMMAND_RETURN_OK
- : print_error(client, error);
-}
-
-static enum command_return
-handle_playlistmove(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
-{
- char *playlist = argv[1];
- unsigned from, to;
-
- if (!check_unsigned(client, &from, argv[2]))
- return COMMAND_RETURN_ERROR;
- if (!check_unsigned(client, &to, argv[3]))
- return COMMAND_RETURN_ERROR;
-
- GError *error = NULL;
- return spl_move_index(playlist, from, to, &error)
- ? COMMAND_RETURN_OK
- : print_error(client, error);
-}
-
-static enum command_return
-handle_update(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
-{
- const char *path = NULL;
- unsigned ret;
-
- assert(argc <= 2);
- if (argc == 2) {
- path = argv[1];
-
- if (*path == 0 || strcmp(path, "/") == 0)
- /* backwards compatibility with MPD 0.15 */
- path = NULL;
- else if (!uri_safe_local(path)) {
- command_error(client, ACK_ERROR_ARG,
- "Malformed path");
- return COMMAND_RETURN_ERROR;
- }
- }
-
- ret = update_enqueue(path, false);
- if (ret > 0) {
- client_printf(client, "updating_db: %i\n", ret);
- return COMMAND_RETURN_OK;
- } else {
- command_error(client, ACK_ERROR_UPDATE_ALREADY,
- "already updating");
- return COMMAND_RETURN_ERROR;
- }
-}
-
-static enum command_return
-handle_rescan(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
-{
- const char *path = NULL;
- unsigned ret;
-
- assert(argc <= 2);
- if (argc == 2) {
- path = argv[1];
-
- if (!uri_safe_local(path)) {
- command_error(client, ACK_ERROR_ARG,
- "Malformed path");
- return COMMAND_RETURN_ERROR;
- }
- }
-
- ret = update_enqueue(path, true);
- if (ret > 0) {
- client_printf(client, "updating_db: %i\n", ret);
- return COMMAND_RETURN_OK;
- } else {
- command_error(client, ACK_ERROR_UPDATE_ALREADY,
- "already updating");
- return COMMAND_RETURN_ERROR;
- }
-}
-
-static enum command_return
-handle_next(G_GNUC_UNUSED struct client *client,
- G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
-{
- /* single mode is not considered when this is user who
- * wants to change song. */
- const bool single = g_playlist.queue.single;
- g_playlist.queue.single = false;
-
- playlist_next(&g_playlist, client->player_control);
-
- g_playlist.queue.single = single;
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
-handle_previous(G_GNUC_UNUSED struct client *client,
- G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
-{
- playlist_previous(&g_playlist, client->player_control);
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
-handle_prio(struct client *client, int argc, char *argv[])
-{
- unsigned priority;
-
- if (!check_unsigned(client, &priority, argv[1]))
- return COMMAND_RETURN_ERROR;
-
- if (priority > 0xff) {
- command_error(client, ACK_ERROR_ARG,
- "Priority out of range: %s", argv[1]);
- return COMMAND_RETURN_ERROR;
- }
-
- for (int i = 2; i < argc; ++i) {
- unsigned start_position, end_position;
- if (!check_range(client, &start_position, &end_position,
- argv[i]))
- return COMMAND_RETURN_ERROR;
-
- enum playlist_result result =
- playlist_set_priority(&g_playlist,
- client->player_control,
- start_position, end_position,
- priority);
- if (result != PLAYLIST_RESULT_SUCCESS)
- return print_playlist_result(client, result);
- }
-
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
-handle_prioid(struct client *client, int argc, char *argv[])
-{
- unsigned priority;
-
- if (!check_unsigned(client, &priority, argv[1]))
- return COMMAND_RETURN_ERROR;
-
- if (priority > 0xff) {
- command_error(client, ACK_ERROR_ARG,
- "Priority out of range: %s", argv[1]);
- return COMMAND_RETURN_ERROR;
- }
-
- for (int i = 2; i < argc; ++i) {
- unsigned song_id;
- if (!check_unsigned(client, &song_id, argv[i]))
- return COMMAND_RETURN_ERROR;
-
- enum playlist_result result =
- playlist_set_priority_id(&g_playlist,
- client->player_control,
- song_id, priority);
- if (result != PLAYLIST_RESULT_SUCCESS)
- return print_playlist_result(client, result);
- }
-
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
-handle_listall(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
-{
- const char *directory = "";
-
- if (argc == 2)
- directory = argv[1];
-
- GError *error = NULL;
- return printAllIn(client, directory, &error)
- ? COMMAND_RETURN_OK
- : print_error(client, error);
-}
-
-static enum command_return
-handle_setvol(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
-{
- unsigned level;
- bool success;
-
- if (!check_unsigned(client, &level, argv[1]))
- return COMMAND_RETURN_ERROR;
-
- if (level > 100) {
- command_error(client, ACK_ERROR_ARG, "Invalid volume value");
- return COMMAND_RETURN_ERROR;
- }
-
- success = volume_level_change(level);
- if (!success) {
- command_error(client, ACK_ERROR_SYSTEM,
- "problems setting volume");
- return COMMAND_RETURN_ERROR;
- }
-
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
-handle_repeat(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
-{
- bool status;
- if (!check_bool(client, &status, argv[1]))
- return COMMAND_RETURN_ERROR;
-
- playlist_set_repeat(&g_playlist, client->player_control, status);
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
-handle_single(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
-{
- bool status;
- if (!check_bool(client, &status, argv[1]))
- return COMMAND_RETURN_ERROR;
-
- playlist_set_single(&g_playlist, client->player_control, status);
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
-handle_consume(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
-{
- bool status;
- if (!check_bool(client, &status, argv[1]))
- return COMMAND_RETURN_ERROR;
-
- playlist_set_consume(&g_playlist, status);
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
-handle_random(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
-{
- bool status;
- if (!check_bool(client, &status, argv[1]))
- return COMMAND_RETURN_ERROR;
-
- playlist_set_random(&g_playlist, client->player_control, status);
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
-handle_stats(struct client *client,
- G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
-{
- return stats_print(client);
-}
-
-static enum command_return
-handle_clearerror(G_GNUC_UNUSED struct client *client,
- G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
-{
- pc_clear_error(client->player_control);
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
-handle_list(struct client *client, int argc, char *argv[])
-{
- struct locate_item_list *conditionals;
- int tagType = locate_parse_type(argv[1]);
-
- if (tagType < 0) {
- command_error(client, ACK_ERROR_ARG, "\"%s\" is not known", argv[1]);
- return COMMAND_RETURN_ERROR;
- }
-
- if (tagType == LOCATE_TAG_ANY_TYPE) {
- command_error(client, ACK_ERROR_ARG,
- "\"any\" is not a valid return tag type");
- return COMMAND_RETURN_ERROR;
- }
-
- /* for compatibility with < 0.12.0 */
- if (argc == 3) {
- if (tagType != TAG_ALBUM) {
- command_error(client, ACK_ERROR_ARG,
- "should be \"%s\" for 3 arguments",
- tag_item_names[TAG_ALBUM]);
- return COMMAND_RETURN_ERROR;
- }
-
- locate_item_list_parse(argv + 1, argc - 1);
-
- conditionals = locate_item_list_new(1);
- conditionals->items[0].tag = TAG_ARTIST;
- conditionals->items[0].needle = g_strdup(argv[2]);
- } else {
- conditionals =
- locate_item_list_parse(argv + 2, argc - 2);
- if (conditionals == NULL) {
- command_error(client, ACK_ERROR_ARG,
- "not able to parse args");
- return COMMAND_RETURN_ERROR;
- }
- }
-
- GError *error = NULL;
- enum command_return ret =
- listAllUniqueTags(client, tagType, conditionals, &error)
- ? COMMAND_RETURN_OK
- : print_error(client, error);
-
- locate_item_list_free(conditionals);
-
- return ret;
-}
-
-static enum command_return
-handle_move(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
-{
- unsigned start, end;
- int to;
- enum playlist_result result;
-
- if (!check_range(client, &start, &end, argv[1]))
- return COMMAND_RETURN_ERROR;
- if (!check_int(client, &to, argv[2]))
- return COMMAND_RETURN_ERROR;
- result = playlist_move_range(&g_playlist, client->player_control,
- start, end, to);
- return print_playlist_result(client, result);
-}
-
-static enum command_return
-handle_moveid(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
-{
- unsigned id;
- int to;
- enum playlist_result result;
-
- if (!check_unsigned(client, &id, argv[1]))
- return COMMAND_RETURN_ERROR;
- if (!check_int(client, &to, argv[2]))
- return COMMAND_RETURN_ERROR;
- result = playlist_move_id(&g_playlist, client->player_control,
- id, to);
- return print_playlist_result(client, result);
-}
-
-static enum command_return
-handle_swap(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
-{
- unsigned song1, song2;
- enum playlist_result result;
-
- if (!check_unsigned(client, &song1, argv[1]))
- return COMMAND_RETURN_ERROR;
- if (!check_unsigned(client, &song2, argv[2]))
- return COMMAND_RETURN_ERROR;
- result = playlist_swap_songs(&g_playlist, client->player_control,
- song1, song2);
- return print_playlist_result(client, result);
-}
-
-static enum command_return
-handle_swapid(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
-{
- unsigned id1, id2;
- enum playlist_result result;
-
- if (!check_unsigned(client, &id1, argv[1]))
- return COMMAND_RETURN_ERROR;
- if (!check_unsigned(client, &id2, argv[2]))
- return COMMAND_RETURN_ERROR;
- result = playlist_swap_songs_id(&g_playlist, client->player_control,
- id1, id2);
- return print_playlist_result(client, result);
-}
-
-static enum command_return
-handle_seek(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
-{
- unsigned song, seek_time;
- enum playlist_result result;
-
- if (!check_unsigned(client, &song, argv[1]))
- return COMMAND_RETURN_ERROR;
- if (!check_unsigned(client, &seek_time, argv[2]))
- return COMMAND_RETURN_ERROR;
-
- result = playlist_seek_song(&g_playlist, client->player_control,
- song, seek_time);
- return print_playlist_result(client, result);
-}
-
-static enum command_return
-handle_seekid(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
-{
- unsigned id, seek_time;
- enum playlist_result result;
-
- if (!check_unsigned(client, &id, argv[1]))
- return COMMAND_RETURN_ERROR;
- if (!check_unsigned(client, &seek_time, argv[2]))
- return COMMAND_RETURN_ERROR;
-
- result = playlist_seek_song_id(&g_playlist, client->player_control,
- id, seek_time);
- return print_playlist_result(client, result);
-}
-
-static enum command_return
-handle_seekcur(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
-{
- const char *p = argv[1];
- bool relative = *p == '+' || *p == '-';
- int seek_time;
- if (!check_int(client, &seek_time, p))
- return COMMAND_RETURN_ERROR;
-
- enum playlist_result result =
- playlist_seek_current(&g_playlist, client->player_control,
- seek_time, relative);
- return print_playlist_result(client, result);
-}
-
-static enum command_return
-handle_listallinfo(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
-{
- const char *directory = "";
-
- if (argc == 2)
- directory = argv[1];
-
- GError *error = NULL;
- return printInfoForAllIn(client, directory, &error)
- ? COMMAND_RETURN_OK
- : print_error(client, error);
-}
-
-static enum command_return
-handle_ping(G_GNUC_UNUSED struct client *client,
- G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
-{
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
-handle_password(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
-{
- unsigned permission = 0;
-
- if (getPermissionFromPassword(argv[1], &permission) < 0) {
- command_error(client, ACK_ERROR_PASSWORD, "incorrect password");
- return COMMAND_RETURN_ERROR;
- }
-
- client_set_permission(client, permission);
-
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
-handle_crossfade(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
-{
- unsigned xfade_time;
-
- if (!check_unsigned(client, &xfade_time, argv[1]))
- return COMMAND_RETURN_ERROR;
- pc_set_cross_fade(client->player_control, xfade_time);
-
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
-handle_mixrampdb(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
-{
- float db;
-
- if (!check_float(client, &db, argv[1]))
- return COMMAND_RETURN_ERROR;
- pc_set_mixramp_db(client->player_control, db);
-
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
-handle_mixrampdelay(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
-{
- float delay_secs;
-
- if (!check_float(client, &delay_secs, argv[1]))
- return COMMAND_RETURN_ERROR;
- pc_set_mixramp_delay(client->player_control, delay_secs);
-
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
-handle_enableoutput(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
-{
- unsigned device;
- bool ret;
-
- if (!check_unsigned(client, &device, argv[1]))
- return COMMAND_RETURN_ERROR;
-
- ret = audio_output_enable_index(device);
- if (!ret) {
- command_error(client, ACK_ERROR_NO_EXIST,
- "No such audio output");
- return COMMAND_RETURN_ERROR;
- }
-
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
-handle_disableoutput(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
-{
- unsigned device;
- bool ret;
-
- if (!check_unsigned(client, &device, argv[1]))
- return COMMAND_RETURN_ERROR;
-
- ret = audio_output_disable_index(device);
- if (!ret) {
- command_error(client, ACK_ERROR_NO_EXIST,
- "No such audio output");
- return COMMAND_RETURN_ERROR;
- }
-
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
-handle_devices(struct client *client,
- G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
-{
- printAudioDevices(client);
-
- return COMMAND_RETURN_OK;
-}
-
-/* don't be fooled, this is the command handler for "commands" command */
-static enum command_return
-handle_commands(struct client *client,
- G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]);
-
-static enum command_return
-handle_not_commands(struct client *client,
- G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]);
-
-static enum command_return
-handle_config(struct client *client,
- G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
-{
- if (!client_is_local(client)) {
- command_error(client, ACK_ERROR_PERMISSION,
- "Command only permitted to local clients");
- return COMMAND_RETURN_ERROR;
- }
-
- const char *path = mapper_get_music_directory_utf8();
- if (path != NULL)
- client_printf(client, "music_directory: %s\n", path);
-
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
-handle_playlistclear(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
-{
- GError *error = NULL;
- return spl_clear(argv[1], &error)
- ? COMMAND_RETURN_OK
- : print_error(client, error);
-}
-
-static enum command_return
-handle_playlistadd(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
-{
- char *playlist = argv[1];
- char *uri = argv[2];
-
- bool success;
- GError *error = NULL;
- if (uri_has_scheme(uri)) {
- if (!uri_supported_scheme(uri)) {
- command_error(client, ACK_ERROR_NO_EXIST,
- "unsupported URI scheme");
- return COMMAND_RETURN_ERROR;
- }
-
- success = spl_append_uri(uri, playlist, &error);
- } else
- success = addAllInToStoredPlaylist(uri, playlist, &error);
-
- if (!success && error == NULL) {
- command_error(client, ACK_ERROR_NO_EXIST,
- "directory or file not found");
- return COMMAND_RETURN_ERROR;
- }
-
- return success ? COMMAND_RETURN_OK : print_error(client, error);
-}
-
-static enum command_return
-handle_listplaylists(struct client *client,
- G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
-{
- GError *error = NULL;
- GPtrArray *list = spl_list(&error);
- if (list == NULL)
- return print_error(client, error);
-
- print_spl_list(client, list);
- spl_list_free(list);
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
-handle_replay_gain_mode(struct client *client,
- G_GNUC_UNUSED int argc, char *argv[])
-{
- if (!replay_gain_set_mode_string(argv[1])) {
- command_error(client, ACK_ERROR_ARG,
- "Unrecognized replay gain mode");
- return COMMAND_RETURN_ERROR;
- }
-
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
-handle_replay_gain_status(struct client *client,
- G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
-{
- client_printf(client, "replay_gain_mode: %s\n",
- replay_gain_get_mode_string());
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
-handle_idle(struct client *client,
- G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
-{
- unsigned flags = 0, j;
- int i;
- const char *const* idle_names;
-
- idle_names = idle_get_names();
- for (i = 1; i < argc; ++i) {
- if (!argv[i])
- continue;
-
- for (j = 0; idle_names[j]; ++j) {
- if (!g_ascii_strcasecmp(argv[i], idle_names[j])) {
- flags |= (1 << j);
- }
- }
- }
-
- /* No argument means that the client wants to receive everything */
- if (flags == 0)
- flags = ~0;
-
- /* enable "idle" mode on this client */
- client_idle_wait(client, flags);
-
- /* return value is "1" so the caller won't print "OK" */
- return 1;
-}
-
-#ifdef ENABLE_SQLITE
-struct sticker_song_find_data {
- struct client *client;
- const char *name;
-};
-
-static void
-sticker_song_find_print_cb(struct song *song, const char *value,
- gpointer user_data)
-{
- struct sticker_song_find_data *data = user_data;
-
- song_print_uri(data->client, song);
- sticker_print_value(data->client, data->name, value);
-}
-
-static enum command_return
-handle_sticker_song(struct client *client, int argc, char *argv[])
-{
- /* get song song_id key */
- if (argc == 5 && strcmp(argv[1], "get") == 0) {
- struct song *song;
- char *value;
-
- song = db_get_song(argv[3]);
- if (song == NULL) {
- command_error(client, ACK_ERROR_NO_EXIST,
- "no such song");
- return COMMAND_RETURN_ERROR;
- }
-
- value = sticker_song_get_value(song, argv[4]);
- if (value == NULL) {
- command_error(client, ACK_ERROR_NO_EXIST,
- "no such sticker");
- return COMMAND_RETURN_ERROR;
- }
-
- sticker_print_value(client, argv[4], value);
- g_free(value);
-
- return COMMAND_RETURN_OK;
- /* list song song_id */
- } else if (argc == 4 && strcmp(argv[1], "list") == 0) {
- struct song *song;
- struct sticker *sticker;
-
- song = db_get_song(argv[3]);
- if (song == NULL) {
- command_error(client, ACK_ERROR_NO_EXIST,
- "no such song");
- return COMMAND_RETURN_ERROR;
- }
-
- sticker = sticker_song_get(song);
- if (sticker) {
- sticker_print(client, sticker);
- sticker_free(sticker);
- }
-
- return COMMAND_RETURN_OK;
- /* set song song_id id key */
- } else if (argc == 6 && strcmp(argv[1], "set") == 0) {
- struct song *song;
- bool ret;
-
- song = db_get_song(argv[3]);
- if (song == NULL) {
- command_error(client, ACK_ERROR_NO_EXIST,
- "no such song");
- return COMMAND_RETURN_ERROR;
- }
-
- ret = sticker_song_set_value(song, argv[4], argv[5]);
- if (!ret) {
- command_error(client, ACK_ERROR_SYSTEM,
- "failed to set sticker value");
- return COMMAND_RETURN_ERROR;
- }
-
- return COMMAND_RETURN_OK;
- /* delete song song_id [key] */
- } else if ((argc == 4 || argc == 5) &&
- strcmp(argv[1], "delete") == 0) {
- struct song *song;
- bool ret;
-
- song = db_get_song(argv[3]);
- if (song == NULL) {
- command_error(client, ACK_ERROR_NO_EXIST,
- "no such song");
- return COMMAND_RETURN_ERROR;
- }
-
- ret = argc == 4
- ? sticker_song_delete(song)
- : sticker_song_delete_value(song, argv[4]);
- if (!ret) {
- command_error(client, ACK_ERROR_SYSTEM,
- "no such sticker");
- return COMMAND_RETURN_ERROR;
- }
-
- return COMMAND_RETURN_OK;
- /* find song dir key */
- } else if (argc == 5 && strcmp(argv[1], "find") == 0) {
- /* "sticker find song a/directory name" */
- struct directory *directory;
- bool success;
- struct sticker_song_find_data data = {
- .client = client,
- .name = argv[4],
- };
-
- db_lock();
- directory = db_get_directory(argv[3]);
- if (directory == NULL) {
- db_unlock();
- command_error(client, ACK_ERROR_NO_EXIST,
- "no such directory");
- return COMMAND_RETURN_ERROR;
- }
-
- success = sticker_song_find(directory, data.name,
- sticker_song_find_print_cb, &data);
- db_unlock();
- if (!success) {
- command_error(client, ACK_ERROR_SYSTEM,
- "failed to set search sticker database");
- return COMMAND_RETURN_ERROR;
- }
-
- return COMMAND_RETURN_OK;
- } else {
- command_error(client, ACK_ERROR_ARG, "bad request");
- return COMMAND_RETURN_ERROR;
- }
-}
-
-static enum command_return
-handle_sticker(struct client *client, int argc, char *argv[])
-{
- assert(argc >= 4);
-
- if (!sticker_enabled()) {
- command_error(client, ACK_ERROR_UNKNOWN,
- "sticker database is disabled");
- return COMMAND_RETURN_ERROR;
- }
-
- if (strcmp(argv[2], "song") == 0)
- return handle_sticker_song(client, argc, argv);
- else {
- command_error(client, ACK_ERROR_ARG,
- "unknown sticker domain");
- return COMMAND_RETURN_ERROR;
- }
-}
-#endif
-
-static enum command_return
-handle_subscribe(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
-{
- assert(argc == 2);
-
- switch (client_subscribe(client, argv[1])) {
- case CLIENT_SUBSCRIBE_OK:
- return COMMAND_RETURN_OK;
-
- case CLIENT_SUBSCRIBE_INVALID:
- command_error(client, ACK_ERROR_ARG,
- "invalid channel name");
- return COMMAND_RETURN_ERROR;
-
- case CLIENT_SUBSCRIBE_ALREADY:
- command_error(client, ACK_ERROR_EXIST,
- "already subscribed to this channel");
- return COMMAND_RETURN_ERROR;
-
- case CLIENT_SUBSCRIBE_FULL:
- command_error(client, ACK_ERROR_EXIST,
- "subscription list is full");
- return COMMAND_RETURN_ERROR;
- }
-
- /* unreachable */
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
-handle_unsubscribe(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
-{
- assert(argc == 2);
-
- if (client_unsubscribe(client, argv[1]))
- return COMMAND_RETURN_OK;
- else {
- command_error(client, ACK_ERROR_NO_EXIST,
- "not subscribed to this channel");
- return COMMAND_RETURN_ERROR;
- }
-}
-
-struct channels_context {
- GStringChunk *chunk;
-
- GHashTable *channels;
-};
-
-static void
-collect_channels(gpointer data, gpointer user_data)
-{
- struct channels_context *context = user_data;
- const struct client *client = data;
-
- for (GSList *i = client->subscriptions; i != NULL;
- i = g_slist_next(i)) {
- const char *channel = i->data;
-
- if (g_hash_table_lookup(context->channels, channel) == NULL) {
- char *channel2 = g_string_chunk_insert(context->chunk,
- channel);
- g_hash_table_insert(context->channels, channel2,
- context);
- }
- }
-}
-
-static void
-print_channel(gpointer key, G_GNUC_UNUSED gpointer value, gpointer user_data)
-{
- struct client *client = user_data;
- const char *channel = key;
-
- client_printf(client, "channel: %s\n", channel);
-}
-
-static enum command_return
-handle_channels(struct client *client,
- G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
-{
- assert(argc == 1);
-
- struct channels_context context = {
- .chunk = g_string_chunk_new(1024),
- .channels = g_hash_table_new(g_str_hash, g_str_equal),
- };
-
- client_list_foreach(collect_channels, &context);
-
- g_hash_table_foreach(context.channels, print_channel, client);
-
- g_hash_table_destroy(context.channels);
- g_string_chunk_free(context.chunk);
-
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
-handle_read_messages(struct client *client,
- G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
-{
- assert(argc == 1);
-
- GSList *messages = client_read_messages(client);
-
- for (GSList *i = messages; i != NULL; i = g_slist_next(i)) {
- struct client_message *msg = i->data;
-
- client_printf(client, "channel: %s\nmessage: %s\n",
- msg->channel, msg->message);
- client_message_free(msg);
- }
-
- g_slist_free(messages);
-
- return COMMAND_RETURN_OK;
-}
-
-struct send_message_context {
- struct client_message msg;
-
- bool sent;
-};
-
-static void
-send_message(gpointer data, gpointer user_data)
-{
- struct send_message_context *context = user_data;
- struct client *client = data;
-
- if (client_push_message(client, &context->msg))
- context->sent = true;
-}
-
-static enum command_return
-handle_send_message(struct client *client,
- G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
-{
- assert(argc == 3);
-
- if (!client_message_valid_channel_name(argv[1])) {
- command_error(client, ACK_ERROR_ARG,
- "invalid channel name");
- return COMMAND_RETURN_ERROR;
- }
-
- struct send_message_context context = {
- .sent = false,
- };
-
- client_message_init(&context.msg, argv[1], argv[2]);
-
- client_list_foreach(send_message, &context);
-
- client_message_deinit(&context.msg);
-
- if (context.sent)
- return COMMAND_RETURN_OK;
- else {
- command_error(client, ACK_ERROR_NO_EXIST,
- "nobody is subscribed to this channel");
- return COMMAND_RETURN_ERROR;
- }
-}
-
-/**
- * The command registry.
- *
- * This array must be sorted!
- */
-static const struct command commands[] = {
- { "add", PERMISSION_ADD, 1, 1, handle_add },
- { "addid", PERMISSION_ADD, 1, 2, handle_addid },
- { "channels", PERMISSION_READ, 0, 0, handle_channels },
- { "clear", PERMISSION_CONTROL, 0, 0, handle_clear },
- { "clearerror", PERMISSION_CONTROL, 0, 0, handle_clearerror },
- { "close", PERMISSION_NONE, -1, -1, handle_close },
- { "commands", PERMISSION_NONE, 0, 0, handle_commands },
- { "config", PERMISSION_ADMIN, 0, 0, handle_config },
- { "consume", PERMISSION_CONTROL, 1, 1, handle_consume },
- { "count", PERMISSION_READ, 2, -1, handle_count },
- { "crossfade", PERMISSION_CONTROL, 1, 1, handle_crossfade },
- { "currentsong", PERMISSION_READ, 0, 0, handle_currentsong },
- { "decoders", PERMISSION_READ, 0, 0, handle_decoders },
- { "delete", PERMISSION_CONTROL, 1, 1, handle_delete },
- { "deleteid", PERMISSION_CONTROL, 1, 1, handle_deleteid },
- { "disableoutput", PERMISSION_ADMIN, 1, 1, handle_disableoutput },
- { "enableoutput", PERMISSION_ADMIN, 1, 1, handle_enableoutput },
- { "find", PERMISSION_READ, 2, -1, handle_find },
- { "findadd", PERMISSION_READ, 2, -1, handle_findadd},
- { "idle", PERMISSION_READ, 0, -1, handle_idle },
- { "kill", PERMISSION_ADMIN, -1, -1, handle_kill },
- { "list", PERMISSION_READ, 1, -1, handle_list },
- { "listall", PERMISSION_READ, 0, 1, handle_listall },
- { "listallinfo", PERMISSION_READ, 0, 1, handle_listallinfo },
- { "listplaylist", PERMISSION_READ, 1, 1, handle_listplaylist },
- { "listplaylistinfo", PERMISSION_READ, 1, 1, handle_listplaylistinfo },
- { "listplaylists", PERMISSION_READ, 0, 0, handle_listplaylists },
- { "load", PERMISSION_ADD, 1, 2, handle_load },
- { "lsinfo", PERMISSION_READ, 0, 1, handle_lsinfo },
- { "mixrampdb", PERMISSION_CONTROL, 1, 1, handle_mixrampdb },
- { "mixrampdelay", PERMISSION_CONTROL, 1, 1, handle_mixrampdelay },
- { "move", PERMISSION_CONTROL, 2, 2, handle_move },
- { "moveid", PERMISSION_CONTROL, 2, 2, handle_moveid },
- { "next", PERMISSION_CONTROL, 0, 0, handle_next },
- { "notcommands", PERMISSION_NONE, 0, 0, handle_not_commands },
- { "outputs", PERMISSION_READ, 0, 0, handle_devices },
- { "password", PERMISSION_NONE, 1, 1, handle_password },
- { "pause", PERMISSION_CONTROL, 0, 1, handle_pause },
- { "ping", PERMISSION_NONE, 0, 0, handle_ping },
- { "play", PERMISSION_CONTROL, 0, 1, handle_play },
- { "playid", PERMISSION_CONTROL, 0, 1, handle_playid },
- { "playlist", PERMISSION_READ, 0, 0, handle_playlist },
- { "playlistadd", PERMISSION_CONTROL, 2, 2, handle_playlistadd },
- { "playlistclear", PERMISSION_CONTROL, 1, 1, handle_playlistclear },
- { "playlistdelete", PERMISSION_CONTROL, 2, 2, handle_playlistdelete },
- { "playlistfind", PERMISSION_READ, 2, -1, handle_playlistfind },
- { "playlistid", PERMISSION_READ, 0, 1, handle_playlistid },
- { "playlistinfo", PERMISSION_READ, 0, 1, handle_playlistinfo },
- { "playlistmove", PERMISSION_CONTROL, 3, 3, handle_playlistmove },
- { "playlistsearch", PERMISSION_READ, 2, -1, handle_playlistsearch },
- { "plchanges", PERMISSION_READ, 1, 1, handle_plchanges },
- { "plchangesposid", PERMISSION_READ, 1, 1, handle_plchangesposid },
- { "previous", PERMISSION_CONTROL, 0, 0, handle_previous },
- { "prio", PERMISSION_CONTROL, 2, -1, handle_prio },
- { "prioid", PERMISSION_CONTROL, 2, -1, handle_prioid },
- { "random", PERMISSION_CONTROL, 1, 1, handle_random },
- { "readmessages", PERMISSION_READ, 0, 0, handle_read_messages },
- { "rename", PERMISSION_CONTROL, 2, 2, handle_rename },
- { "repeat", PERMISSION_CONTROL, 1, 1, handle_repeat },
- { "replay_gain_mode", PERMISSION_CONTROL, 1, 1,
- handle_replay_gain_mode },
- { "replay_gain_status", PERMISSION_READ, 0, 0,
- handle_replay_gain_status },
- { "rescan", PERMISSION_CONTROL, 0, 1, handle_rescan },
- { "rm", PERMISSION_CONTROL, 1, 1, handle_rm },
- { "save", PERMISSION_CONTROL, 1, 1, handle_save },
- { "search", PERMISSION_READ, 2, -1, handle_search },
- { "searchadd", PERMISSION_ADD, 2, -1, handle_searchadd },
- { "searchaddpl", PERMISSION_CONTROL, 3, -1, handle_searchaddpl },
- { "seek", PERMISSION_CONTROL, 2, 2, handle_seek },
- { "seekcur", PERMISSION_CONTROL, 1, 1, handle_seekcur },
- { "seekid", PERMISSION_CONTROL, 2, 2, handle_seekid },
- { "sendmessage", PERMISSION_CONTROL, 2, 2, handle_send_message },
- { "setvol", PERMISSION_CONTROL, 1, 1, handle_setvol },
- { "shuffle", PERMISSION_CONTROL, 0, 1, handle_shuffle },
- { "single", PERMISSION_CONTROL, 1, 1, handle_single },
- { "stats", PERMISSION_READ, 0, 0, handle_stats },
- { "status", PERMISSION_READ, 0, 0, handle_status },
-#ifdef ENABLE_SQLITE
- { "sticker", PERMISSION_ADMIN, 3, -1, handle_sticker },
-#endif
- { "stop", PERMISSION_CONTROL, 0, 0, handle_stop },
- { "subscribe", PERMISSION_READ, 1, 1, handle_subscribe },
- { "swap", PERMISSION_CONTROL, 2, 2, handle_swap },
- { "swapid", PERMISSION_CONTROL, 2, 2, handle_swapid },
- { "tagtypes", PERMISSION_READ, 0, 0, handle_tagtypes },
- { "unsubscribe", PERMISSION_READ, 1, 1, handle_unsubscribe },
- { "update", PERMISSION_CONTROL, 0, 1, handle_update },
- { "urlhandlers", PERMISSION_READ, 0, 0, handle_urlhandlers },
-};
-
-static const unsigned num_commands = sizeof(commands) / sizeof(commands[0]);
-
-static bool
-command_available(G_GNUC_UNUSED const struct command *cmd)
-{
-#ifdef ENABLE_SQLITE
- if (strcmp(cmd->cmd, "sticker") == 0)
- return sticker_enabled();
-#endif
-
- return true;
-}
-
-/* don't be fooled, this is the command handler for "commands" command */
-static enum command_return
-handle_commands(struct client *client,
- G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
-{
- const unsigned permission = client_get_permission(client);
- const struct command *cmd;
-
- for (unsigned i = 0; i < num_commands; ++i) {
- cmd = &commands[i];
-
- if (cmd->permission == (permission & cmd->permission) &&
- command_available(cmd))
- client_printf(client, "command: %s\n", cmd->cmd);
- }
-
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
-handle_not_commands(struct client *client,
- G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
-{
- const unsigned permission = client_get_permission(client);
- const struct command *cmd;
-
- for (unsigned i = 0; i < num_commands; ++i) {
- cmd = &commands[i];
-
- if (cmd->permission != (permission & cmd->permission))
- client_printf(client, "command: %s\n", cmd->cmd);
- }
-
- return COMMAND_RETURN_OK;
-}
-
-void command_init(void)
-{
-#ifndef NDEBUG
- /* ensure that the command list is sorted */
- for (unsigned i = 0; i < num_commands - 1; ++i)
- assert(strcmp(commands[i].cmd, commands[i + 1].cmd) < 0);
-#endif
-}
-
-void command_finish(void)
-{
-}
-
-static const struct command *
-command_lookup(const char *name)
-{
- unsigned a = 0, b = num_commands, i;
- int cmp;
-
- /* binary search */
- do {
- i = (a + b) / 2;
-
- cmp = strcmp(name, commands[i].cmd);
- if (cmp == 0)
- return &commands[i];
- else if (cmp < 0)
- b = i;
- else if (cmp > 0)
- a = i + 1;
- } while (a < b);
-
- return NULL;
-}
-
-static bool
-command_check_request(const struct command *cmd, struct client *client,
- unsigned permission, int argc, char *argv[])
-{
- int min = cmd->min + 1;
- int max = cmd->max + 1;
-
- if (cmd->permission != (permission & cmd->permission)) {
- if (client != NULL)
- command_error(client, ACK_ERROR_PERMISSION,
- "you don't have permission for \"%s\"",
- cmd->cmd);
- return false;
- }
-
- if (min == 0)
- return true;
-
- if (min == max && max != argc) {
- if (client != NULL)
- command_error(client, ACK_ERROR_ARG,
- "wrong number of arguments for \"%s\"",
- argv[0]);
- return false;
- } else if (argc < min) {
- if (client != NULL)
- command_error(client, ACK_ERROR_ARG,
- "too few arguments for \"%s\"", argv[0]);
- return false;
- } else if (argc > max && max /* != 0 */ ) {
- if (client != NULL)
- command_error(client, ACK_ERROR_ARG,
- "too many arguments for \"%s\"", argv[0]);
- return false;
- } else
- return true;
-}
-
-static const struct command *
-command_checked_lookup(struct client *client, unsigned permission,
- int argc, char *argv[])
-{
- const struct command *cmd;
-
- current_command = "";
-
- if (argc == 0)
- return NULL;
-
- cmd = command_lookup(argv[0]);
- if (cmd == NULL) {
- if (client != NULL)
- command_error(client, ACK_ERROR_UNKNOWN,
- "unknown command \"%s\"", argv[0]);
- return NULL;
- }
-
- current_command = cmd->cmd;
-
- if (!command_check_request(cmd, client, permission, argc, argv))
- return NULL;
-
- return cmd;
-}
-
-enum command_return
-command_process(struct client *client, unsigned num, char *line)
-{
- GError *error = NULL;
- int argc;
- char *argv[COMMAND_ARGV_MAX] = { NULL };
- const struct command *cmd;
- enum command_return ret = COMMAND_RETURN_ERROR;
-
- command_list_num = num;
-
- /* get the command name (first word on the line) */
-
- argv[0] = tokenizer_next_word(&line, &error);
- if (argv[0] == NULL) {
- current_command = "";
- if (*line == 0)
- command_error(client, ACK_ERROR_UNKNOWN,
- "No command given");
- else {
- command_error(client, ACK_ERROR_UNKNOWN,
- "%s", error->message);
- g_error_free(error);
- }
- current_command = NULL;
-
- return COMMAND_RETURN_ERROR;
- }
-
- argc = 1;
-
- /* now parse the arguments (quoted or unquoted) */
-
- while (argc < (int)G_N_ELEMENTS(argv) &&
- (argv[argc] =
- tokenizer_next_param(&line, &error)) != NULL)
- ++argc;
-
- /* some error checks; we have to set current_command because
- command_error() expects it to be set */
-
- current_command = argv[0];
-
- if (argc >= (int)G_N_ELEMENTS(argv)) {
- command_error(client, ACK_ERROR_ARG, "Too many arguments");
- current_command = NULL;
- return COMMAND_RETURN_ERROR;
- }
-
- if (*line != 0) {
- command_error(client, ACK_ERROR_ARG,
- "%s", error->message);
- current_command = NULL;
- g_error_free(error);
- return COMMAND_RETURN_ERROR;
- }
-
- /* look up and invoke the command handler */
-
- cmd = command_checked_lookup(client, client_get_permission(client),
- argc, argv);
- if (cmd)
- ret = cmd->handler(client, argc, argv);
-
- current_command = NULL;
- command_list_num = 0;
-
- return ret;
-}
diff --git a/src/command.h b/src/command.h
index 68d1f95e4..9ea5bb52f 100644
--- a/src/command.h
+++ b/src/command.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * 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
@@ -20,27 +20,34 @@
#ifndef MPD_COMMAND_H
#define MPD_COMMAND_H
-#include "ack.h"
-
-#include <glib.h>
-#include <stdbool.h>
-
enum command_return {
- COMMAND_RETURN_ERROR = -1,
- COMMAND_RETURN_OK = 0,
- COMMAND_RETURN_KILL = 10,
- COMMAND_RETURN_CLOSE = 20,
+ /**
+ * The command has succeeded, but the "OK" response was not
+ * yet sent to the client.
+ */
+ COMMAND_RETURN_OK,
+
+ /**
+ * The connection is now in "idle" mode, and no response shall
+ * be generated.
+ */
+ COMMAND_RETURN_IDLE,
+
+ /**
+ * There was an error. The "ACK" response was sent to the
+ * client.
+ */
+ COMMAND_RETURN_ERROR,
+
+ /**
+ * The connection to this client shall be closed.
+ */
+ COMMAND_RETURN_CLOSE,
+
+ /**
+ * The MPD process shall be shut down.
+ */
+ COMMAND_RETURN_KILL,
};
-struct client;
-
-void command_init(void);
-
-void command_finish(void);
-
-enum command_return
-command_process(struct client *client, unsigned num, char *line);
-
-void command_success(struct client *client);
-
#endif
diff --git a/src/conf.c b/src/conf.c
deleted file mode 100644
index a22f2ec91..000000000
--- a/src/conf.c
+++ /dev/null
@@ -1,666 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "conf.h"
-#include "utils.h"
-#include "string_util.h"
-#include "tokenizer.h"
-#include "path.h"
-#include "mpd_error.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <errno.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "config"
-
-#define MAX_STRING_SIZE MPD_PATH_MAX+80
-
-#define CONF_COMMENT '#'
-
-struct config_entry {
- const char *const name;
- const bool repeatable;
- const bool block;
-
- GSList *params;
-};
-
-static struct config_entry config_entries[] = {
- { .name = CONF_MUSIC_DIR, false, false },
- { .name = CONF_PLAYLIST_DIR, false, false },
- { .name = CONF_FOLLOW_INSIDE_SYMLINKS, false, false },
- { .name = CONF_FOLLOW_OUTSIDE_SYMLINKS, false, false },
- { .name = CONF_DB_FILE, false, false },
- { .name = CONF_STICKER_FILE, false, false },
- { .name = CONF_LOG_FILE, false, false },
- { .name = CONF_PID_FILE, false, false },
- { .name = CONF_STATE_FILE, false, false },
- { .name = "restore_paused", false, false },
- { .name = CONF_USER, false, false },
- { .name = CONF_GROUP, false, false },
- { .name = CONF_BIND_TO_ADDRESS, true, false },
- { .name = CONF_PORT, false, false },
- { .name = CONF_LOG_LEVEL, false, false },
- { .name = CONF_ZEROCONF_NAME, false, false },
- { .name = CONF_ZEROCONF_ENABLED, false, false },
- { .name = CONF_PASSWORD, true, false },
- { .name = CONF_DEFAULT_PERMS, false, false },
- { .name = CONF_AUDIO_OUTPUT, true, true },
- { .name = CONF_AUDIO_OUTPUT_FORMAT, false, false },
- { .name = CONF_MIXER_TYPE, false, false },
- { .name = CONF_REPLAYGAIN, false, false },
- { .name = CONF_REPLAYGAIN_PREAMP, false, false },
- { .name = CONF_REPLAYGAIN_MISSING_PREAMP, false, false },
- { .name = CONF_REPLAYGAIN_LIMIT, false, false },
- { .name = CONF_VOLUME_NORMALIZATION, false, false },
- { .name = CONF_SAMPLERATE_CONVERTER, false, false },
- { .name = CONF_AUDIO_BUFFER_SIZE, false, false },
- { .name = CONF_BUFFER_BEFORE_PLAY, false, false },
- { .name = CONF_HTTP_PROXY_HOST, false, false },
- { .name = CONF_HTTP_PROXY_PORT, false, false },
- { .name = CONF_HTTP_PROXY_USER, false, false },
- { .name = CONF_HTTP_PROXY_PASSWORD, false, false },
- { .name = CONF_CONN_TIMEOUT, false, false },
- { .name = CONF_MAX_CONN, false, false },
- { .name = CONF_MAX_PLAYLIST_LENGTH, false, false },
- { .name = CONF_MAX_COMMAND_LIST_SIZE, false, false },
- { .name = CONF_MAX_OUTPUT_BUFFER_SIZE, false, false },
- { .name = CONF_FS_CHARSET, false, false },
- { .name = CONF_ID3V1_ENCODING, false, false },
- { .name = CONF_METADATA_TO_USE, false, false },
- { .name = CONF_SAVE_ABSOLUTE_PATHS, false, false },
- { .name = CONF_DECODER, true, true },
- { .name = CONF_INPUT, true, true },
- { .name = CONF_GAPLESS_MP3_PLAYBACK, false, false },
- { .name = CONF_PLAYLIST_PLUGIN, true, true },
- { .name = CONF_AUTO_UPDATE, false, false },
- { .name = CONF_AUTO_UPDATE_DEPTH, false, false },
- { .name = CONF_DESPOTIFY_USER, false, false },
- { .name = CONF_DESPOTIFY_PASSWORD, false, false},
- { .name = CONF_DESPOTIFY_HIGH_BITRATE, false, false },
- { .name = "filter", true, true },
-};
-
-static bool
-get_bool(const char *value, bool *value_r)
-{
- static const char *t[] = { "yes", "true", "1", NULL };
- static const char *f[] = { "no", "false", "0", NULL };
-
- if (string_array_contains(t, value)) {
- *value_r = true;
- return true;
- }
-
- if (string_array_contains(f, value)) {
- *value_r = false;
- return true;
- }
-
- return false;
-}
-
-struct config_param *
-config_new_param(const char *value, int line)
-{
- struct config_param *ret = g_new(struct config_param, 1);
-
- if (!value)
- ret->value = NULL;
- else
- ret->value = g_strdup(value);
-
- ret->line = line;
-
- ret->num_block_params = 0;
- ret->block_params = NULL;
- ret->used = false;
-
- return ret;
-}
-
-void
-config_param_free(struct config_param *param)
-{
- g_free(param->value);
-
- for (unsigned i = 0; i < param->num_block_params; i++) {
- g_free(param->block_params[i].name);
- g_free(param->block_params[i].value);
- }
-
- if (param->num_block_params)
- g_free(param->block_params);
-
- g_free(param);
-}
-
-static void
-config_param_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
-{
- struct config_param *param = data;
-
- config_param_free(param);
-}
-
-static struct config_entry *
-config_entry_get(const char *name)
-{
- for (unsigned i = 0; i < G_N_ELEMENTS(config_entries); ++i) {
- struct config_entry *entry = &config_entries[i];
- if (strcmp(entry->name, name) == 0)
- return entry;
- }
-
- return NULL;
-}
-
-void config_global_finish(void)
-{
- for (unsigned i = 0; i < G_N_ELEMENTS(config_entries); ++i) {
- struct config_entry *entry = &config_entries[i];
-
- g_slist_foreach(entry->params,
- config_param_free_callback, NULL);
- g_slist_free(entry->params);
- }
-}
-
-void config_global_init(void)
-{
-}
-
-static void
-config_param_check(gpointer data, G_GNUC_UNUSED gpointer user_data)
-{
- struct config_param *param = data;
-
- if (!param->used)
- /* this whole config_param was not queried at all -
- the feature might be disabled at compile time?
- Silently ignore it here. */
- return;
-
- for (unsigned i = 0; i < param->num_block_params; i++) {
- struct block_param *bp = &param->block_params[i];
-
- if (!bp->used)
- g_warning("option '%s' on line %i was not recognized",
- bp->name, bp->line);
- }
-}
-
-void config_global_check(void)
-{
- for (unsigned i = 0; i < G_N_ELEMENTS(config_entries); ++i) {
- struct config_entry *entry = &config_entries[i];
-
- g_slist_foreach(entry->params, config_param_check, NULL);
- }
-}
-
-void
-config_add_block_param(struct config_param * param, const char *name,
- const char *value, int line)
-{
- struct block_param *bp;
-
- assert(config_get_block_param(param, name) == NULL);
-
- param->num_block_params++;
-
- param->block_params = g_realloc(param->block_params,
- param->num_block_params *
- sizeof(param->block_params[0]));
-
- bp = &param->block_params[param->num_block_params - 1];
-
- bp->name = g_strdup(name);
- bp->value = g_strdup(value);
- bp->line = line;
- bp->used = false;
-}
-
-static bool
-config_read_name_value(struct config_param *param, char *input, unsigned line,
- GError **error_r)
-{
- const char *name = tokenizer_next_word(&input, error_r);
- if (name == NULL) {
- assert(*input != 0);
- return false;
- }
-
- const char *value = tokenizer_next_string(&input, error_r);
- if (value == NULL) {
- if (*input == 0) {
- assert(error_r == NULL || *error_r == NULL);
- g_set_error(error_r, config_quark(), 0,
- "Value missing");
- } else {
- assert(error_r == NULL || *error_r != NULL);
- }
-
- return false;
- }
-
- if (*input != 0 && *input != CONF_COMMENT) {
- g_set_error(error_r, config_quark(), 0,
- "Unknown tokens after value");
- return false;
- }
-
- const struct block_param *bp = config_get_block_param(param, name);
- if (bp != NULL) {
- g_set_error(error_r, config_quark(), 0,
- "\"%s\" is duplicate, first defined on line %i",
- name, bp->line);
- return false;
- }
-
- config_add_block_param(param, name, value, line);
- return true;
-}
-
-static struct config_param *
-config_read_block(FILE *fp, int *count, char *string, GError **error_r)
-{
- struct config_param *ret = config_new_param(NULL, *count);
- GError *error = NULL;
-
- while (true) {
- char *line;
-
- line = fgets(string, MAX_STRING_SIZE, fp);
- if (line == NULL) {
- config_param_free(ret);
- g_set_error(error_r, config_quark(), 0,
- "Expected '}' before end-of-file");
- return NULL;
- }
-
- (*count)++;
- line = strchug_fast(line);
- if (*line == 0 || *line == CONF_COMMENT)
- continue;
-
- if (*line == '}') {
- /* end of this block; return from the function
- (and from this "while" loop) */
-
- line = strchug_fast(line + 1);
- if (*line != 0 && *line != CONF_COMMENT) {
- config_param_free(ret);
- g_set_error(error_r, config_quark(), 0,
- "line %i: Unknown tokens after '}'",
- *count);
- return NULL;
- }
-
- return ret;
- }
-
- /* parse name and value */
-
- if (!config_read_name_value(ret, line, *count, &error)) {
- assert(*line != 0);
- config_param_free(ret);
- g_propagate_prefixed_error(error_r, error,
- "line %i: ", *count);
- return NULL;
- }
- }
-}
-
-bool
-config_read_file(const char *file, GError **error_r)
-{
- FILE *fp;
- char string[MAX_STRING_SIZE + 1];
- int count = 0;
- struct config_entry *entry;
- struct config_param *param;
-
- g_debug("loading file %s", file);
-
- if (!(fp = fopen(file, "r"))) {
- g_set_error(error_r, config_quark(), errno,
- "Failed to open %s: %s",
- file, g_strerror(errno));
- return false;
- }
-
- while (fgets(string, MAX_STRING_SIZE, fp)) {
- char *line;
- const char *name, *value;
- GError *error = NULL;
-
- count++;
-
- line = strchug_fast(string);
- if (*line == 0 || *line == CONF_COMMENT)
- continue;
-
- /* the first token in each line is the name, followed
- by either the value or '{' */
-
- name = tokenizer_next_word(&line, &error);
- if (name == NULL) {
- assert(*line != 0);
- g_propagate_prefixed_error(error_r, error,
- "line %i: ", count);
- fclose(fp);
- return false;
- }
-
- /* get the definition of that option, and check the
- "repeatable" flag */
-
- entry = config_entry_get(name);
- if (entry == NULL) {
- g_set_error(error_r, config_quark(), 0,
- "unrecognized parameter in config file at "
- "line %i: %s\n", count, name);
- fclose(fp);
- return false;
- }
-
- if (entry->params != NULL && !entry->repeatable) {
- param = entry->params->data;
- g_set_error(error_r, config_quark(), 0,
- "config parameter \"%s\" is first defined "
- "on line %i and redefined on line %i\n",
- name, param->line, count);
- fclose(fp);
- return false;
- }
-
- /* now parse the block or the value */
-
- if (entry->block) {
- /* it's a block, call config_read_block() */
-
- if (*line != '{') {
- g_set_error(error_r, config_quark(), 0,
- "line %i: '{' expected", count);
- fclose(fp);
- return false;
- }
-
- line = strchug_fast(line + 1);
- if (*line != 0 && *line != CONF_COMMENT) {
- g_set_error(error_r, config_quark(), 0,
- "line %i: Unknown tokens after '{'",
- count);
- fclose(fp);
- return false;
- }
-
- param = config_read_block(fp, &count, string, error_r);
- if (param == NULL) {
- fclose(fp);
- return false;
- }
- } else {
- /* a string value */
-
- value = tokenizer_next_string(&line, &error);
- if (value == NULL) {
- if (*line == 0)
- g_set_error(error_r, config_quark(), 0,
- "line %i: Value missing",
- count);
- else {
- g_set_error(error_r, config_quark(), 0,
- "line %i: %s", count,
- error->message);
- g_error_free(error);
- }
-
- fclose(fp);
- return false;
- }
-
- if (*line != 0 && *line != CONF_COMMENT) {
- g_set_error(error_r, config_quark(), 0,
- "line %i: Unknown tokens after value",
- count);
- fclose(fp);
- return false;
- }
-
- param = config_new_param(value, count);
- }
-
- entry->params = g_slist_append(entry->params, param);
- }
- fclose(fp);
-
- return true;
-}
-
-const struct config_param *
-config_get_next_param(const char *name, const struct config_param * last)
-{
- struct config_entry *entry;
- GSList *node;
- struct config_param *param;
-
- entry = config_entry_get(name);
- if (entry == NULL)
- return NULL;
-
- node = entry->params;
-
- if (last) {
- node = g_slist_find(node, last);
- if (node == NULL)
- return NULL;
-
- node = g_slist_next(node);
- }
-
- if (node == NULL)
- return NULL;
-
- param = node->data;
- param->used = true;
- return param;
-}
-
-const char *
-config_get_string(const char *name, const char *default_value)
-{
- const struct config_param *param = config_get_param(name);
-
- if (param == NULL)
- return default_value;
-
- return param->value;
-}
-
-char *
-config_dup_path(const char *name, GError **error_r)
-{
- assert(error_r != NULL);
- assert(*error_r == NULL);
-
- const struct config_param *param = config_get_param(name);
- if (param == NULL)
- return NULL;
-
- char *path = parsePath(param->value, error_r);
- if (G_UNLIKELY(path == NULL))
- g_prefix_error(error_r,
- "Invalid path in \"%s\" at line %i: ",
- name, param->line);
-
- return path;
-}
-
-unsigned
-config_get_unsigned(const char *name, unsigned default_value)
-{
- const struct config_param *param = config_get_param(name);
- long value;
- char *endptr;
-
- if (param == NULL)
- return default_value;
-
- value = strtol(param->value, &endptr, 0);
- if (*endptr != 0 || value < 0)
- MPD_ERROR("Not a valid non-negative number in line %i",
- param->line);
-
- return (unsigned)value;
-}
-
-unsigned
-config_get_positive(const char *name, unsigned default_value)
-{
- const struct config_param *param = config_get_param(name);
- long value;
- char *endptr;
-
- if (param == NULL)
- return default_value;
-
- value = strtol(param->value, &endptr, 0);
- if (*endptr != 0)
- MPD_ERROR("Not a valid number in line %i", param->line);
-
- if (value <= 0)
- MPD_ERROR("Not a positive number in line %i", param->line);
-
- return (unsigned)value;
-}
-
-const struct block_param *
-config_get_block_param(const struct config_param * param, const char *name)
-{
- if (param == NULL)
- return NULL;
-
- for (unsigned i = 0; i < param->num_block_params; i++) {
- if (0 == strcmp(name, param->block_params[i].name)) {
- struct block_param *bp = &param->block_params[i];
- bp->used = true;
- return bp;
- }
- }
-
- return NULL;
-}
-
-bool config_get_bool(const char *name, bool default_value)
-{
- const struct config_param *param = config_get_param(name);
- bool success, value;
-
- if (param == NULL)
- return default_value;
-
- success = get_bool(param->value, &value);
- if (!success)
- MPD_ERROR("%s is not a boolean value (yes, true, 1) or "
- "(no, false, 0) on line %i\n",
- name, param->line);
-
- return value;
-}
-
-const char *
-config_get_block_string(const struct config_param *param, const char *name,
- const char *default_value)
-{
- const struct block_param *bp = config_get_block_param(param, name);
-
- if (bp == NULL)
- return default_value;
-
- return bp->value;
-}
-
-char *
-config_dup_block_path(const struct config_param *param, const char *name,
- GError **error_r)
-{
- assert(error_r != NULL);
- assert(*error_r == NULL);
-
- const struct block_param *bp = config_get_block_param(param, name);
- if (bp == NULL)
- return NULL;
-
- char *path = parsePath(bp->value, error_r);
- if (G_UNLIKELY(path == NULL))
- g_prefix_error(error_r,
- "Invalid path in \"%s\" at line %i: ",
- name, bp->line);
-
- return path;
-}
-
-unsigned
-config_get_block_unsigned(const struct config_param *param, const char *name,
- unsigned default_value)
-{
- const struct block_param *bp = config_get_block_param(param, name);
- long value;
- char *endptr;
-
- if (bp == NULL)
- return default_value;
-
- value = strtol(bp->value, &endptr, 0);
- if (*endptr != 0)
- MPD_ERROR("Not a valid number in line %i", bp->line);
-
- if (value < 0)
- MPD_ERROR("Not a positive number in line %i", bp->line);
-
- return (unsigned)value;
-}
-
-bool
-config_get_block_bool(const struct config_param *param, const char *name,
- bool default_value)
-{
- const struct block_param *bp = config_get_block_param(param, name);
- bool success, value;
-
- if (bp == NULL)
- return default_value;
-
- success = get_bool(bp->value, &value);
- if (!success)
- MPD_ERROR("%s is not a boolean value (yes, true, 1) or "
- "(no, false, 0) on line %i\n",
- name, bp->line);
-
- return value;
-}
diff --git a/src/conf.h b/src/conf.h
index 815c739b1..8eb185fa7 100644
--- a/src/conf.h
+++ b/src/conf.h
@@ -20,208 +20,14 @@
#ifndef MPD_CONF_H
#define MPD_CONF_H
-#include <stdbool.h>
-#include <glib.h>
-
-#define CONF_MUSIC_DIR "music_directory"
-#define CONF_PLAYLIST_DIR "playlist_directory"
-#define CONF_FOLLOW_INSIDE_SYMLINKS "follow_inside_symlinks"
-#define CONF_FOLLOW_OUTSIDE_SYMLINKS "follow_outside_symlinks"
-#define CONF_DB_FILE "db_file"
-#define CONF_STICKER_FILE "sticker_file"
-#define CONF_LOG_FILE "log_file"
-#define CONF_PID_FILE "pid_file"
-#define CONF_STATE_FILE "state_file"
-#define CONF_USER "user"
-#define CONF_GROUP "group"
-#define CONF_BIND_TO_ADDRESS "bind_to_address"
-#define CONF_PORT "port"
-#define CONF_LOG_LEVEL "log_level"
-#define CONF_ZEROCONF_NAME "zeroconf_name"
-#define CONF_ZEROCONF_ENABLED "zeroconf_enabled"
-#define CONF_PASSWORD "password"
-#define CONF_DEFAULT_PERMS "default_permissions"
-#define CONF_AUDIO_OUTPUT "audio_output"
-#define CONF_AUDIO_FILTER "filter"
-#define CONF_AUDIO_OUTPUT_FORMAT "audio_output_format"
-#define CONF_MIXER_TYPE "mixer_type"
-#define CONF_REPLAYGAIN "replaygain"
-#define CONF_REPLAYGAIN_PREAMP "replaygain_preamp"
-#define CONF_REPLAYGAIN_MISSING_PREAMP "replaygain_missing_preamp"
-#define CONF_REPLAYGAIN_LIMIT "replaygain_limit"
-#define CONF_VOLUME_NORMALIZATION "volume_normalization"
-#define CONF_SAMPLERATE_CONVERTER "samplerate_converter"
-#define CONF_AUDIO_BUFFER_SIZE "audio_buffer_size"
-#define CONF_BUFFER_BEFORE_PLAY "buffer_before_play"
-#define CONF_HTTP_PROXY_HOST "http_proxy_host"
-#define CONF_HTTP_PROXY_PORT "http_proxy_port"
-#define CONF_HTTP_PROXY_USER "http_proxy_user"
-#define CONF_HTTP_PROXY_PASSWORD "http_proxy_password"
-#define CONF_CONN_TIMEOUT "connection_timeout"
-#define CONF_MAX_CONN "max_connections"
-#define CONF_MAX_PLAYLIST_LENGTH "max_playlist_length"
-#define CONF_MAX_COMMAND_LIST_SIZE "max_command_list_size"
-#define CONF_MAX_OUTPUT_BUFFER_SIZE "max_output_buffer_size"
-#define CONF_FS_CHARSET "filesystem_charset"
-#define CONF_ID3V1_ENCODING "id3v1_encoding"
-#define CONF_METADATA_TO_USE "metadata_to_use"
-#define CONF_SAVE_ABSOLUTE_PATHS "save_absolute_paths_in_playlists"
-#define CONF_DECODER "decoder"
-#define CONF_INPUT "input"
-#define CONF_GAPLESS_MP3_PLAYBACK "gapless_mp3_playback"
-#define CONF_PLAYLIST_PLUGIN "playlist_plugin"
-#define CONF_AUTO_UPDATE "auto_update"
-#define CONF_AUTO_UPDATE_DEPTH "auto_update_depth"
-#define CONF_DESPOTIFY_USER "despotify_user"
-#define CONF_DESPOTIFY_PASSWORD "despotify_password"
-#define CONF_DESPOTIFY_HIGH_BITRATE "despotify_high_bitrate"
+#include "ConfigGlobal.hxx"
+#include "ConfigOption.hxx"
+#include "ConfigData.hxx"
+#include "gcc.h"
#define DEFAULT_PLAYLIST_MAX_LENGTH (1024*16)
#define DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS false
#define MAX_FILTER_CHAIN_LENGTH 255
-struct block_param {
- char *name;
- char *value;
- int line;
-
- /**
- * This flag is false when nobody has queried the value of
- * this option yet.
- */
- bool used;
-};
-
-struct config_param {
- char *value;
- unsigned int line;
-
- struct block_param *block_params;
- unsigned num_block_params;
-
- /**
- * This flag is false when nobody has queried the value of
- * this option yet.
- */
- bool used;
-};
-
-/**
- * A GQuark for GError instances, resulting from malformed
- * configuration.
- */
-G_GNUC_CONST
-static inline GQuark
-config_quark(void)
-{
- return g_quark_from_static_string("config");
-}
-
-void config_global_init(void);
-void config_global_finish(void);
-
-/**
- * Call this function after all configuration has been evaluated. It
- * checks for unused parameters, and logs warnings.
- */
-void config_global_check(void);
-
-bool
-config_read_file(const char *file, GError **error_r);
-
-/* don't free the returned value
- set _last_ to NULL to get first entry */
-G_GNUC_PURE
-const struct config_param *
-config_get_next_param(const char *name, const struct config_param *last);
-
-G_GNUC_PURE
-static inline const struct config_param *
-config_get_param(const char *name)
-{
- return config_get_next_param(name, NULL);
-}
-
-/* Note on G_GNUC_PURE: Some of the functions declared pure are not
- really pure in strict sense. They have side effect such that they
- validate parameter's value and signal an error if it's invalid.
- However, if the argument was already validated or we don't care
- about the argument at all, this may be ignored so in the end, we
- should be fine with calling those functions pure. */
-
-G_GNUC_PURE
-const char *
-config_get_string(const char *name, const char *default_value);
-
-/**
- * Returns an optional configuration variable which contains an
- * absolute path. If there is a tilde prefix, it is expanded.
- * Returns NULL if the value is not present. If the path could not be
- * parsed, returns NULL and sets the error.
- *
- * The return value must be freed with g_free().
- */
-G_GNUC_MALLOC
-char *
-config_dup_path(const char *name, GError **error_r);
-
-G_GNUC_PURE
-unsigned
-config_get_unsigned(const char *name, unsigned default_value);
-
-G_GNUC_PURE
-unsigned
-config_get_positive(const char *name, unsigned default_value);
-
-G_GNUC_PURE
-const struct block_param *
-config_get_block_param(const struct config_param *param, const char *name);
-
-G_GNUC_PURE
-bool config_get_bool(const char *name, bool default_value);
-
-G_GNUC_PURE
-const char *
-config_get_block_string(const struct config_param *param, const char *name,
- const char *default_value);
-
-G_GNUC_MALLOC
-static inline char *
-config_dup_block_string(const struct config_param *param, const char *name,
- const char *default_value)
-{
- return g_strdup(config_get_block_string(param, name, default_value));
-}
-
-/**
- * Same as config_dup_path(), but looks up the setting in the
- * specified block.
- */
-G_GNUC_MALLOC
-char *
-config_dup_block_path(const struct config_param *param, const char *name,
- GError **error_r);
-
-G_GNUC_PURE
-unsigned
-config_get_block_unsigned(const struct config_param *param, const char *name,
- unsigned default_value);
-
-G_GNUC_PURE
-bool
-config_get_block_bool(const struct config_param *param, const char *name,
- bool default_value);
-
-G_GNUC_MALLOC
-struct config_param *
-config_new_param(const char *value, int line);
-
-void
-config_param_free(struct config_param *param);
-
-void
-config_add_block_param(struct config_param * param, const char *name,
- const char *value, int line);
-
#endif
diff --git a/src/crossfade.c b/src/crossfade.c
deleted file mode 100644
index 46a0dff30..000000000
--- a/src/crossfade.c
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "crossfade.h"
-#include "chunk.h"
-#include "audio_format.h"
-#include "tag.h"
-
-#include <assert.h>
-#include <string.h>
-#include <stdlib.h>
-#include <glib.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "crossfade"
-
-#ifdef G_OS_WIN32
-static char *
-strtok_r(char *str, const char *delim, G_GNUC_UNUSED char **saveptr)
-{
- return strtok(str, delim);
-}
-#endif
-
-static float mixramp_interpolate(char *ramp_list, float required_db)
-{
- float db, secs, last_db = nan(""), last_secs = 0;
- char *ramp_str, *save_str = NULL;
-
- /* ramp_list is a string of pairs of dBs and seconds that describe the
- * volume profile. Delimiters are semi-colons between pairs and spaces
- * between the dB and seconds of a pair.
- * The dB values must be monotonically increasing for this to work. */
-
- while (1) {
- /* Parse the dB tokens out of the input string. */
- ramp_str = strtok_r(ramp_list, " ", &save_str);
-
- /* Tell strtok to continue next time round. */
- ramp_list = NULL;
-
- /* Parse the dB value. */
- if (NULL == ramp_str) {
- return nan("");
- }
- db = (float)atof(ramp_str);
-
- /* Parse the time. */
- ramp_str = strtok_r(NULL, ";", &save_str);
- if (NULL == ramp_str) {
- return nan("");
- }
- secs = (float)atof(ramp_str);
-
- /* Check for exact match. */
- if (db == required_db) {
- return secs;
- }
-
- /* Save if too quiet. */
- if (db < required_db) {
- last_db = db;
- last_secs = secs;
- continue;
- }
-
- /* If required db < any stored value, use the least. */
- if (isnan(last_db)) {
- return secs;
- }
-
- /* Finally, interpolate linearly. */
- secs = last_secs + (required_db - last_db) * (secs - last_secs) / (db - last_db);
- return secs;
- }
-}
-
-unsigned cross_fade_calc(float duration, float total_time,
- float mixramp_db, float mixramp_delay,
- float replay_gain_db, float replay_gain_prev_db,
- char *mixramp_start, char *mixramp_prev_end,
- const struct audio_format *af,
- const struct audio_format *old_format,
- unsigned max_chunks)
-{
- unsigned int chunks = 0;
- float chunks_f;
- float mixramp_overlap;
-
- if (duration < 0 || duration >= total_time ||
- /* we can't crossfade when the audio formats are different */
- !audio_format_equals(af, old_format))
- return 0;
-
- assert(duration >= 0);
- assert(audio_format_valid(af));
-
- chunks_f = (float)audio_format_time_to_size(af) / (float)CHUNK_SIZE;
-
- if (isnan(mixramp_delay) || !(mixramp_start) || !(mixramp_prev_end)) {
- chunks = (chunks_f * duration + 0.5);
- } else {
- /* Calculate mixramp overlap. */
- mixramp_overlap = mixramp_interpolate(mixramp_start, mixramp_db - replay_gain_db)
- + mixramp_interpolate(mixramp_prev_end, mixramp_db - replay_gain_prev_db);
- if (!isnan(mixramp_overlap) && (mixramp_delay <= mixramp_overlap)) {
- chunks = (chunks_f * (mixramp_overlap - mixramp_delay));
- g_debug("will overlap %d chunks, %fs", chunks,
- mixramp_overlap - mixramp_delay);
- }
- }
-
- if (chunks > max_chunks) {
- chunks = max_chunks;
- g_warning("audio_buffer_size too small for computed MixRamp overlap");
- }
-
- return chunks;
-}
diff --git a/src/crossfade.h b/src/crossfade.h
deleted file mode 100644
index d581dbfe0..000000000
--- a/src/crossfade.h
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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_CROSSFADE_H
-#define MPD_CROSSFADE_H
-
-struct audio_format;
-struct music_chunk;
-
-/**
- * Calculate how many music pipe chunks should be used for crossfading.
- *
- * @param duration the requested crossfade duration
- * @param total_time total_time the duration of the new song
- * @param mixramp_db the current mixramp_db setting
- * @param mixramp_delay the current mixramp_delay setting
- * @param replay_gain_db the ReplayGain adjustment used for this song
- * @param replay_gain_prev_db the ReplayGain adjustment used on the last song
- * @param mixramp_start the next songs mixramp_start tag
- * @param mixramp_prev_end the last songs mixramp_end setting
- * @param af the audio format of the new song
- * @param old_format the audio format of the current song
- * @param max_chunks the maximum number of chunks
- * @return the number of chunks for crossfading, or 0 if cross fading
- * should be disabled for this song change
- */
-unsigned cross_fade_calc(float duration, float total_time,
- float mixramp_db, float mixramp_delay,
- float replay_gain_db, float replay_gain_prev_db,
- char *mixramp_start, char *mixramp_prev_end,
- const struct audio_format *af,
- const struct audio_format *old_format,
- unsigned max_chunks);
-
-#endif
diff --git a/src/cue/CueParser.cxx b/src/cue/CueParser.cxx
new file mode 100644
index 000000000..e2ae6f2dd
--- /dev/null
+++ b/src/cue/CueParser.cxx
@@ -0,0 +1,323 @@
+/*
+ * 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 "CueParser.hxx"
+#include "util/StringUtil.hxx"
+#include "Song.hxx"
+#include "Tag.hxx"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+
+CueParser::CueParser()
+ :state(HEADER), tag(new Tag()),
+ filename(nullptr),
+ current(nullptr),
+ previous(nullptr),
+ finished(nullptr),
+ end(false) {}
+
+CueParser::~CueParser()
+{
+ delete tag;
+ g_free(filename);
+
+ if (current != nullptr)
+ current->Free();
+
+ if (previous != nullptr)
+ previous->Free();
+
+ if (finished != nullptr)
+ finished->Free();
+}
+
+static const char *
+cue_next_word(char *p, char **pp)
+{
+ assert(p >= *pp);
+ assert(!g_ascii_isspace(*p));
+
+ const char *word = p;
+ while (*p != 0 && !g_ascii_isspace(*p))
+ ++p;
+
+ *p = 0;
+ *pp = p + 1;
+ return word;
+}
+
+static const char *
+cue_next_quoted(char *p, char **pp)
+{
+ assert(p >= *pp);
+ assert(p[-1] == '"');
+
+ char *end = strchr(p, '"');
+ if (end == nullptr) {
+ /* syntax error - ignore it silently */
+ *pp = p + strlen(p);
+ return p;
+ }
+
+ *end = 0;
+ *pp = end + 1;
+
+ return p;
+}
+
+static const char *
+cue_next_token(char **pp)
+{
+ char *p = strchug_fast(*pp);
+ if (*p == 0)
+ return nullptr;
+
+ return cue_next_word(p, pp);
+}
+
+static const char *
+cue_next_value(char **pp)
+{
+ char *p = strchug_fast(*pp);
+ if (*p == 0)
+ return nullptr;
+
+ if (*p == '"')
+ return cue_next_quoted(p + 1, pp);
+ else
+ return cue_next_word(p, pp);
+}
+
+static void
+cue_add_tag(Tag &tag, enum tag_type type, char *p)
+{
+ const char *value = cue_next_value(&p);
+ if (value != nullptr)
+ tag.AddItem(type, value);
+
+}
+
+static void
+cue_parse_rem(char *p, Tag &tag)
+{
+ const char *type = cue_next_token(&p);
+ if (type == nullptr)
+ return;
+
+ enum tag_type type2 = tag_name_parse_i(type);
+ if (type2 != TAG_NUM_OF_ITEM_TYPES)
+ cue_add_tag(tag, type2, p);
+}
+
+Tag *
+CueParser::GetCurrentTag()
+{
+ if (state == HEADER)
+ return tag;
+ else if (state == TRACK)
+ return current->tag;
+ else
+ return nullptr;
+}
+
+static int
+cue_parse_position(const char *p)
+{
+ char *endptr;
+ unsigned long minutes = strtoul(p, &endptr, 10);
+ if (endptr == p || *endptr != ':')
+ return -1;
+
+ p = endptr + 1;
+ unsigned long seconds = strtoul(p, &endptr, 10);
+ if (endptr == p || *endptr != ':')
+ return -1;
+
+ p = endptr + 1;
+ unsigned long frames = strtoul(p, &endptr, 10);
+ if (endptr == p || *endptr != 0)
+ return -1;
+
+ return minutes * 60000 + seconds * 1000 + frames * 1000 / 75;
+}
+
+void
+CueParser::Commit()
+{
+ /* the caller of this library must call cue_parser_get() often
+ enough */
+ assert(finished == nullptr);
+ assert(!end);
+
+ if (current == nullptr)
+ return;
+
+ finished = previous;
+ previous = current;
+ current = nullptr;
+}
+
+void
+CueParser::Feed2(char *p)
+{
+ assert(!end);
+ assert(p != nullptr);
+
+ const char *command = cue_next_token(&p);
+ if (command == nullptr)
+ return;
+
+ if (strcmp(command, "REM") == 0) {
+ Tag *current_tag = GetCurrentTag();
+ if (current_tag != nullptr)
+ cue_parse_rem(p, *current_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
+ Knowledgebase: "At top-level this will specify the
+ CD artist, while at track-level it specifies the
+ track artist." */
+
+ enum tag_type type = state == TRACK
+ ? TAG_ARTIST
+ : TAG_ALBUM_ARTIST;
+
+ Tag *current_tag = GetCurrentTag();
+ if (current_tag != nullptr)
+ cue_add_tag(*current_tag, type, p);
+ } else if (strcmp(command, "TITLE") == 0) {
+ if (state == HEADER)
+ cue_add_tag(*tag, TAG_ALBUM, p);
+ else if (state == TRACK)
+ cue_add_tag(*current->tag, TAG_TITLE, p);
+ } else if (strcmp(command, "FILE") == 0) {
+ Commit();
+
+ const char *new_filename = cue_next_value(&p);
+ if (new_filename == nullptr)
+ return;
+
+ const char *type = cue_next_token(&p);
+ if (type == nullptr)
+ return;
+
+ if (strcmp(type, "WAVE") != 0 &&
+ strcmp(type, "MP3") != 0 &&
+ strcmp(type, "AIFF") != 0) {
+ state = IGNORE_FILE;
+ return;
+ }
+
+ state = WAVE;
+ g_free(filename);
+ filename = g_strdup(new_filename);
+ } else if (state == IGNORE_FILE) {
+ return;
+ } else if (strcmp(command, "TRACK") == 0) {
+ Commit();
+
+ const char *nr = cue_next_token(&p);
+ if (nr == nullptr)
+ return;
+
+ const char *type = cue_next_token(&p);
+ if (type == nullptr)
+ return;
+
+ if (strcmp(type, "AUDIO") != 0) {
+ state = IGNORE_TRACK;
+ return;
+ }
+
+ state = TRACK;
+ current = Song::NewRemote(filename);
+ assert(current->tag == nullptr);
+ current->tag = new Tag(*tag);
+ current->tag->AddItem(TAG_TRACK, nr);
+ last_updated = false;
+ } else if (state == IGNORE_TRACK) {
+ return;
+ } else if (state == TRACK && strcmp(command, "INDEX") == 0) {
+ const char *nr = cue_next_token(&p);
+ if (nr == nullptr)
+ return;
+
+ const char *position = cue_next_token(&p);
+ if (position == nullptr)
+ return;
+
+ int position_ms = cue_parse_position(position);
+ if (position_ms < 0)
+ return;
+
+ if (!last_updated && previous != nullptr &&
+ previous->start_ms < (unsigned)position_ms) {
+ last_updated = true;
+ previous->end_ms = position_ms;
+ previous->tag->time =
+ (previous->end_ms - previous->start_ms + 500) / 1000;
+ }
+
+ current->start_ms = position_ms;
+ }
+}
+
+void
+CueParser::Feed(const char *line)
+{
+ assert(!end);
+ assert(line != nullptr);
+
+ char *allocated = g_strdup(line);
+ Feed2(allocated);
+ g_free(allocated);
+}
+
+void
+CueParser::Finish()
+{
+ if (end)
+ /* has already been called, ignore */
+ return;
+
+ Commit();
+ end = true;
+}
+
+Song *
+CueParser::Get()
+{
+ if (finished == nullptr && end) {
+ /* cue_parser_finish() has been called already:
+ deliver all remaining (partial) results */
+ assert(current == nullptr);
+
+ finished = previous;
+ previous = nullptr;
+ }
+
+ Song *song = finished;
+ finished = nullptr;
+ return song;
+}
diff --git a/src/cue/CueParser.hxx b/src/cue/CueParser.hxx
new file mode 100644
index 000000000..5cb51200f
--- /dev/null
+++ b/src/cue/CueParser.hxx
@@ -0,0 +1,131 @@
+/*
+ * 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_CUE_PARSER_HXX
+#define MPD_CUE_PARSER_HXX
+
+#include "check.h"
+#include "gcc.h"
+
+struct Song;
+struct Tag;
+
+class CueParser {
+ enum {
+ /**
+ * Parsing the CUE header.
+ */
+ HEADER,
+
+ /**
+ * Parsing a "FILE ... WAVE".
+ */
+ WAVE,
+
+ /**
+ * Ignore everything until the next "FILE".
+ */
+ IGNORE_FILE,
+
+ /**
+ * Parsing a "TRACK ... AUDIO".
+ */
+ TRACK,
+
+ /**
+ * Ignore everything until the next "TRACK".
+ */
+ IGNORE_TRACK,
+ } state;
+
+ Tag *tag;
+
+ char *filename;
+
+ /**
+ * The song currently being edited.
+ */
+ Song *current;
+
+ /**
+ * The previous song. It is remembered because its end_time
+ * will be set to the current song's start time.
+ */
+ Song *previous;
+
+ /**
+ * A song that is completely finished and can be returned to
+ * the caller via cue_parser_get().
+ */
+ Song *finished;
+
+ /**
+ * Set to true after previous.end_time has been updated to the
+ * start time of the current song.
+ */
+ bool last_updated;
+
+ /**
+ * Tracks whether cue_parser_finish() has been called. If
+ * true, then all remaining (partial) results will be
+ * delivered by cue_parser_get().
+ */
+ bool end;
+
+public:
+ CueParser();
+ ~CueParser();
+
+ /**
+ * Feed a text line from the CUE file into the parser. Call
+ * cue_parser_get() after this to see if a song has been finished.
+ */
+ void Feed(const char *line);
+
+ /**
+ * Tell the parser that the end of the file has been reached. Call
+ * cue_parser_get() after this to see if a song has been finished.
+ * This procedure must be done twice!
+ */
+ void Finish();
+
+ /**
+ * Check if a song was finished by the last cue_parser_feed() or
+ * cue_parser_finish() call.
+ *
+ * @return a song object that must be freed by the caller, or NULL if
+ * no song was finished at this time
+ */
+ Song *Get();
+
+private:
+ gcc_pure
+ Tag *GetCurrentTag();
+
+ /**
+ * Commit the current song. It will be moved to "previous",
+ * so the next song may soon edit its end time (using the next
+ * song's start time).
+ */
+ void Commit();
+
+ void Feed2(char *p);
+};
+
+#endif
diff --git a/src/cue/cue_parser.c b/src/cue/cue_parser.c
deleted file mode 100644
index 9ccc3bcdd..000000000
--- a/src/cue/cue_parser.c
+++ /dev/null
@@ -1,401 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "cue_parser.h"
-#include "string_util.h"
-#include "song.h"
-#include "tag.h"
-
-#include <assert.h>
-#include <stdlib.h>
-
-struct cue_parser {
- enum {
- /**
- * Parsing the CUE header.
- */
- HEADER,
-
- /**
- * Parsing a "FILE ... WAVE".
- */
- WAVE,
-
- /**
- * Ignore everything until the next "FILE".
- */
- IGNORE_FILE,
-
- /**
- * Parsing a "TRACK ... AUDIO".
- */
- TRACK,
-
- /**
- * Ignore everything until the next "TRACK".
- */
- IGNORE_TRACK,
- } state;
-
- struct tag *tag;
-
- char *filename;
-
- /**
- * The song currently being edited.
- */
- struct song *current;
-
- /**
- * The previous song. It is remembered because its end_time
- * will be set to the current song's start time.
- */
- struct song *previous;
-
- /**
- * A song that is completely finished and can be returned to
- * the caller via cue_parser_get().
- */
- struct song *finished;
-
- /**
- * Set to true after previous.end_time has been updated to the
- * start time of the current song.
- */
- bool last_updated;
-
- /**
- * Tracks whether cue_parser_finish() has been called. If
- * true, then all remaining (partial) results will be
- * delivered by cue_parser_get().
- */
- bool end;
-};
-
-struct cue_parser *
-cue_parser_new(void)
-{
- struct cue_parser *parser = g_new(struct cue_parser, 1);
- parser->state = HEADER;
- parser->tag = tag_new();
- parser->filename = NULL;
- parser->current = NULL;
- parser->previous = NULL;
- parser->finished = NULL;
- parser->end = false;
- return parser;
-}
-
-void
-cue_parser_free(struct cue_parser *parser)
-{
- tag_free(parser->tag);
- g_free(parser->filename);
-
- if (parser->current != NULL)
- song_free(parser->current);
-
- if (parser->previous != NULL)
- song_free(parser->previous);
-
- if (parser->finished != NULL)
- song_free(parser->finished);
-
- g_free(parser);
-}
-
-static const char *
-cue_next_word(char *p, char **pp)
-{
- assert(p >= *pp);
- assert(!g_ascii_isspace(*p));
-
- const char *word = p;
- while (*p != 0 && !g_ascii_isspace(*p))
- ++p;
-
- *p = 0;
- *pp = p + 1;
- return word;
-}
-
-static const char *
-cue_next_quoted(char *p, char **pp)
-{
- assert(p >= *pp);
- assert(p[-1] == '"');
-
- char *end = strchr(p, '"');
- if (end == NULL) {
- /* syntax error - ignore it silently */
- *pp = p + strlen(p);
- return p;
- }
-
- *end = 0;
- *pp = end + 1;
-
- return p;
-}
-
-static const char *
-cue_next_token(char **pp)
-{
- char *p = strchug_fast(*pp);
- if (*p == 0)
- return NULL;
-
- return cue_next_word(p, pp);
-}
-
-static const char *
-cue_next_value(char **pp)
-{
- char *p = strchug_fast(*pp);
- if (*p == 0)
- return NULL;
-
- if (*p == '"')
- return cue_next_quoted(p + 1, pp);
- else
- return cue_next_word(p, pp);
-}
-
-static void
-cue_add_tag(struct tag *tag, enum tag_type type, char *p)
-{
- const char *value = cue_next_value(&p);
- if (value != NULL)
- tag_add_item(tag, type, value);
-
-}
-
-static void
-cue_parse_rem(char *p, struct tag *tag)
-{
- const char *type = cue_next_token(&p);
- if (type == NULL)
- return;
-
- enum tag_type type2 = tag_name_parse_i(type);
- if (type2 != TAG_NUM_OF_ITEM_TYPES)
- cue_add_tag(tag, type2, p);
-}
-
-static struct tag *
-cue_current_tag(struct cue_parser *parser)
-{
- if (parser->state == HEADER)
- return parser->tag;
- else if (parser->state == TRACK)
- return parser->current->tag;
- else
- return NULL;
-}
-
-static int
-cue_parse_position(const char *p)
-{
- char *endptr;
- unsigned long minutes = strtoul(p, &endptr, 10);
- if (endptr == p || *endptr != ':')
- return -1;
-
- p = endptr + 1;
- unsigned long seconds = strtoul(p, &endptr, 10);
- if (endptr == p || *endptr != ':')
- return -1;
-
- p = endptr + 1;
- unsigned long frames = strtoul(p, &endptr, 10);
- if (endptr == p || *endptr != 0)
- return -1;
-
- return minutes * 60000 + seconds * 1000 + frames * 1000 / 75;
-}
-
-/**
- * Commit the current song. It will be moved to "previous", so the
- * next song may soon edit its end time (using the next song's start
- * time).
- */
-static void
-cue_parser_commit(struct cue_parser *parser)
-{
- /* the caller of this library must call cue_parser_get() often
- enough */
- assert(parser->finished == NULL);
- assert(!parser->end);
-
- if (parser->current == NULL)
- return;
-
- parser->finished = parser->previous;
- parser->previous = parser->current;
- parser->current = NULL;
-}
-
-static void
-cue_parser_feed2(struct cue_parser *parser, char *p)
-{
- assert(parser != NULL);
- assert(!parser->end);
- assert(p != NULL);
-
- const char *command = cue_next_token(&p);
- if (command == NULL)
- return;
-
- if (strcmp(command, "REM") == 0) {
- struct tag *tag = cue_current_tag(parser);
- if (tag != NULL)
- 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
- Knowledgebase: "At top-level this will specify the
- CD artist, while at track-level it specifies the
- track artist." */
-
- enum tag_type type = parser->state == TRACK
- ? TAG_ARTIST
- : TAG_ALBUM_ARTIST;
-
- struct tag *tag = cue_current_tag(parser);
- if (tag != NULL)
- cue_add_tag(tag, type, p);
- } else if (strcmp(command, "TITLE") == 0) {
- if (parser->state == HEADER)
- cue_add_tag(parser->tag, TAG_ALBUM, p);
- else if (parser->state == TRACK)
- cue_add_tag(parser->current->tag, TAG_TITLE, p);
- } else if (strcmp(command, "FILE") == 0) {
- cue_parser_commit(parser);
-
- const char *filename = cue_next_value(&p);
- if (filename == NULL)
- return;
-
- const char *type = cue_next_token(&p);
- if (type == NULL)
- return;
-
- if (strcmp(type, "WAVE") != 0 &&
- strcmp(type, "MP3") != 0 &&
- strcmp(type, "AIFF") != 0) {
- parser->state = IGNORE_FILE;
- return;
- }
-
- parser->state = WAVE;
- g_free(parser->filename);
- parser->filename = g_strdup(filename);
- } else if (parser->state == IGNORE_FILE) {
- return;
- } else if (strcmp(command, "TRACK") == 0) {
- cue_parser_commit(parser);
-
- const char *nr = cue_next_token(&p);
- if (nr == NULL)
- return;
-
- const char *type = cue_next_token(&p);
- if (type == NULL)
- return;
-
- if (strcmp(type, "AUDIO") != 0) {
- parser->state = IGNORE_TRACK;
- return;
- }
-
- parser->state = TRACK;
- parser->current = song_remote_new(parser->filename);
- assert(parser->current->tag == NULL);
- parser->current->tag = tag_dup(parser->tag);
- tag_add_item(parser->current->tag, TAG_TRACK, nr);
- parser->last_updated = false;
- } else if (parser->state == IGNORE_TRACK) {
- return;
- } else if (parser->state == TRACK && strcmp(command, "INDEX") == 0) {
- const char *nr = cue_next_token(&p);
- if (nr == NULL)
- return;
-
- const char *position = cue_next_token(&p);
- if (position == NULL)
- return;
-
- int position_ms = cue_parse_position(position);
- if (position_ms < 0)
- return;
-
- if (!parser->last_updated && parser->previous != NULL &&
- parser->previous->start_ms < (unsigned)position_ms) {
- parser->last_updated = true;
- parser->previous->end_ms = position_ms;
- parser->previous->tag->time =
- (parser->previous->end_ms - parser->previous->start_ms + 500) / 1000;
- }
-
- parser->current->start_ms = position_ms;
- }
-}
-
-void
-cue_parser_feed(struct cue_parser *parser, const char *line)
-{
- assert(parser != NULL);
- assert(!parser->end);
- assert(line != NULL);
-
- char *allocated = g_strdup(line);
- cue_parser_feed2(parser, allocated);
- g_free(allocated);
-}
-
-void
-cue_parser_finish(struct cue_parser *parser)
-{
- if (parser->end)
- /* has already been called, ignore */
- return;
-
- cue_parser_commit(parser);
- parser->end = true;
-}
-
-struct song *
-cue_parser_get(struct cue_parser *parser)
-{
- assert(parser != NULL);
-
- if (parser->finished == NULL && parser->end) {
- /* cue_parser_finish() has been called already:
- deliver all remaining (partial) results */
- assert(parser->current == NULL);
-
- parser->finished = parser->previous;
- parser->previous = NULL;
- }
-
- struct song *song = parser->finished;
- parser->finished = NULL;
- return song;
-}
diff --git a/src/cue/cue_parser.h b/src/cue/cue_parser.h
deleted file mode 100644
index d8d695739..000000000
--- a/src/cue/cue_parser.h
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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_CUE_PARSER_H
-#define MPD_CUE_PARSER_H
-
-#include "check.h"
-
-#include <stdbool.h>
-
-struct cue_parser *
-cue_parser_new(void);
-
-void
-cue_parser_free(struct cue_parser *parser);
-
-/**
- * Feed a text line from the CUE file into the parser. Call
- * cue_parser_get() after this to see if a song has been finished.
- */
-void
-cue_parser_feed(struct cue_parser *parser, const char *line);
-
-/**
- * Tell the parser that the end of the file has been reached. Call
- * cue_parser_get() after this to see if a song has been finished.
- * This procedure must be done twice!
- */
-void
-cue_parser_finish(struct cue_parser *parser);
-
-/**
- * Check if a song was finished by the last cue_parser_feed() or
- * cue_parser_finish() call.
- *
- * @return a song object that must be freed by the caller, or NULL if
- * no song was finished at this time
- */
-struct song *
-cue_parser_get(struct cue_parser *parser);
-
-#endif
diff --git a/src/database.c b/src/database.c
deleted file mode 100644
index 8c903bb45..000000000
--- a/src/database.c
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "database.h"
-#include "db_error.h"
-#include "db_save.h"
-#include "db_selection.h"
-#include "db_visitor.h"
-#include "db_plugin.h"
-#include "db/simple_db_plugin.h"
-#include "directory.h"
-#include "stats.h"
-#include "conf.h"
-#include "glib_compat.h"
-
-#include <glib.h>
-
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <assert.h>
-#include <string.h>
-#include <errno.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "database"
-
-static struct db *db;
-static bool db_is_open;
-
-bool
-db_init(const struct config_param *path, GError **error_r)
-{
- assert(db == NULL);
- assert(!db_is_open);
-
- if (path == NULL)
- return true;
-
- struct config_param *param = config_new_param("database", path->line);
- config_add_block_param(param, "path", path->value, path->line);
-
- db = db_plugin_new(&simple_db_plugin, param, error_r);
-
- config_param_free(param);
-
- return db != NULL;
-}
-
-void
-db_finish(void)
-{
- if (db_is_open)
- db_plugin_close(db);
-
- if (db != NULL)
- db_plugin_free(db);
-}
-
-struct directory *
-db_get_root(void)
-{
- assert(db != NULL);
-
- return simple_db_get_root(db);
-}
-
-struct directory *
-db_get_directory(const char *name)
-{
- if (db == NULL)
- return NULL;
-
- struct directory *music_root = db_get_root();
- if (name == NULL)
- return music_root;
-
- struct directory *directory =
- directory_lookup_directory(music_root, name);
- return directory;
-}
-
-struct song *
-db_get_song(const char *file)
-{
- assert(file != NULL);
-
- g_debug("get song: %s", file);
-
- if (db == NULL)
- return NULL;
-
- return db_plugin_get_song(db, file, NULL);
-}
-
-bool
-db_visit(const struct db_selection *selection,
- const struct db_visitor *visitor, void *ctx,
- GError **error_r)
-{
- if (db == NULL) {
- g_set_error_literal(error_r, db_quark(), DB_DISABLED,
- "No database");
- return false;
- }
-
- return db_plugin_visit(db, selection, visitor, ctx, error_r);
-}
-
-bool
-db_walk(const char *uri,
- const struct db_visitor *visitor, void *ctx,
- GError **error_r)
-{
- struct db_selection selection;
- db_selection_init(&selection, uri, true);
-
- return db_visit(&selection, visitor, ctx, error_r);
-}
-
-bool
-db_save(GError **error_r)
-{
- assert(db != NULL);
- assert(db_is_open);
-
- return simple_db_save(db, error_r);
-}
-
-bool
-db_load(GError **error)
-{
- assert(db != NULL);
- assert(!db_is_open);
-
- if (!db_plugin_open(db, error))
- return false;
-
- db_is_open = true;
-
- stats_update();
-
- return true;
-}
-
-time_t
-db_get_mtime(void)
-{
- assert(db != NULL);
- assert(db_is_open);
-
- return simple_db_get_mtime(db);
-}
diff --git a/src/database.h b/src/database.h
deleted file mode 100644
index f877b74d3..000000000
--- a/src/database.h
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * 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_DATABASE_H
-#define MPD_DATABASE_H
-
-#include "gcc.h"
-
-#include <glib.h>
-
-#include <sys/time.h>
-#include <stdbool.h>
-
-struct config_param;
-struct directory;
-struct db_selection;
-struct db_visitor;
-
-/**
- * Initialize the database library.
- *
- * @param path the absolute path of the database file
- */
-bool
-db_init(const struct config_param *path, GError **error_r);
-
-void
-db_finish(void);
-
-/**
- * Returns the root directory object. Returns NULL if there is no
- * configured music directory.
- */
-G_GNUC_PURE
-struct directory *
-db_get_root(void);
-
-/**
- * Caller must lock the #db_mutex.
- */
-gcc_nonnull(1)
-G_GNUC_PURE
-struct directory *
-db_get_directory(const char *name);
-
-gcc_nonnull(1)
-G_GNUC_PURE
-struct song *
-db_get_song(const char *file);
-
-gcc_nonnull(1,2)
-bool
-db_visit(const struct db_selection *selection,
- const struct db_visitor *visitor, void *ctx,
- GError **error_r);
-
-gcc_nonnull(1,2)
-bool
-db_walk(const char *uri,
- const struct db_visitor *visitor, void *ctx,
- GError **error_r);
-
-bool
-db_save(GError **error_r);
-
-bool
-db_load(GError **error);
-
-G_GNUC_PURE
-time_t
-db_get_mtime(void);
-
-/**
- * Returns true if there is a valid database file on the disk.
- */
-G_GNUC_PURE
-static inline bool
-db_exists(void)
-{
- /* mtime is set only if the database file was loaded or saved
- successfully */
- return db_get_mtime() > 0;
-}
-
-#endif
diff --git a/src/db/ProxyDatabasePlugin.cxx b/src/db/ProxyDatabasePlugin.cxx
new file mode 100644
index 000000000..ee557ed11
--- /dev/null
+++ b/src/db/ProxyDatabasePlugin.cxx
@@ -0,0 +1,476 @@
+/*
+ * 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 "ProxyDatabasePlugin.hxx"
+#include "DatabasePlugin.hxx"
+#include "DatabaseSelection.hxx"
+#include "PlaylistVector.hxx"
+#include "Directory.hxx"
+#include "Song.hxx"
+#include "gcc.h"
+#include "conf.h"
+#include "Tag.hxx"
+
+extern "C" {
+#include "db_error.h"
+}
+
+#undef MPD_DIRECTORY_H
+#undef MPD_SONG_H
+#include <mpd/client.h>
+
+#include <cassert>
+#include <string>
+#include <list>
+
+class ProxyDatabase : public Database {
+ std::string host;
+ unsigned port;
+
+ struct mpd_connection *connection;
+ Directory *root;
+
+public:
+ static Database *Create(const config_param &param,
+ GError **error_r);
+
+ virtual bool Open(GError **error_r) override;
+ virtual void Close() override;
+ virtual Song *GetSong(const char *uri_utf8,
+ GError **error_r) const override;
+ virtual void ReturnSong(Song *song) const;
+
+ virtual bool Visit(const DatabaseSelection &selection,
+ VisitDirectory visit_directory,
+ VisitSong visit_song,
+ VisitPlaylist visit_playlist,
+ GError **error_r) const override;
+
+ virtual bool VisitUniqueTags(const DatabaseSelection &selection,
+ enum tag_type tag_type,
+ VisitString visit_string,
+ GError **error_r) const override;
+
+ virtual bool GetStats(const DatabaseSelection &selection,
+ DatabaseStats &stats,
+ GError **error_r) const override;
+
+protected:
+ bool Configure(const config_param &param, GError **error_r);
+};
+
+G_GNUC_CONST
+static inline GQuark
+libmpdclient_quark(void)
+{
+ return g_quark_from_static_string("libmpdclient");
+}
+
+static constexpr struct {
+ enum tag_type d;
+ enum mpd_tag_type s;
+} tag_table[] = {
+ { TAG_ARTIST, MPD_TAG_ARTIST },
+ { TAG_ALBUM, MPD_TAG_ALBUM },
+ { TAG_ALBUM_ARTIST, MPD_TAG_ALBUM_ARTIST },
+ { TAG_TITLE, MPD_TAG_TITLE },
+ { TAG_TRACK, MPD_TAG_TRACK },
+ { TAG_NAME, MPD_TAG_NAME },
+ { TAG_GENRE, MPD_TAG_GENRE },
+ { TAG_DATE, MPD_TAG_DATE },
+ { TAG_COMPOSER, MPD_TAG_COMPOSER },
+ { TAG_PERFORMER, MPD_TAG_PERFORMER },
+ { TAG_COMMENT, MPD_TAG_COMMENT },
+ { TAG_DISC, MPD_TAG_DISC },
+ { TAG_MUSICBRAINZ_ARTISTID, MPD_TAG_MUSICBRAINZ_ARTISTID },
+ { TAG_MUSICBRAINZ_ALBUMID, MPD_TAG_MUSICBRAINZ_ALBUMID },
+ { TAG_MUSICBRAINZ_ALBUMARTISTID,
+ MPD_TAG_MUSICBRAINZ_ALBUMARTISTID },
+ { TAG_MUSICBRAINZ_TRACKID, MPD_TAG_MUSICBRAINZ_TRACKID },
+ { TAG_NUM_OF_ITEM_TYPES, MPD_TAG_COUNT }
+};
+
+G_GNUC_CONST
+static enum mpd_tag_type
+Convert(enum tag_type tag_type)
+{
+ for (auto i = &tag_table[0]; i->d != TAG_NUM_OF_ITEM_TYPES; ++i)
+ if (i->d == tag_type)
+ return i->s;
+
+ return MPD_TAG_COUNT;
+}
+
+static bool
+CheckError(struct mpd_connection *connection, GError **error_r)
+{
+ const auto error = mpd_connection_get_error(connection);
+ if (error == MPD_ERROR_SUCCESS)
+ return true;
+
+ g_set_error_literal(error_r, libmpdclient_quark(), (int)error,
+ mpd_connection_get_error_message(connection));
+ mpd_connection_clear_error(connection);
+ return false;
+}
+
+Database *
+ProxyDatabase::Create(const config_param &param, GError **error_r)
+{
+ ProxyDatabase *db = new ProxyDatabase();
+ if (!db->Configure(param, error_r)) {
+ delete db;
+ db = NULL;
+ }
+
+ return db;
+}
+
+bool
+ProxyDatabase::Configure(const config_param &param, GError **)
+{
+ host = param.GetBlockValue("host", "");
+ port = param.GetBlockValue("port", 0u);
+
+ return true;
+}
+
+bool
+ProxyDatabase::Open(GError **error_r)
+{
+ connection = mpd_connection_new(host.empty() ? NULL : host.c_str(),
+ port, 0);
+ if (connection == NULL) {
+ g_set_error_literal(error_r, libmpdclient_quark(),
+ (int)MPD_ERROR_OOM, "Out of memory");
+ return false;
+ }
+
+ if (!CheckError(connection, error_r)) {
+ mpd_connection_free(connection);
+ return false;
+ }
+
+ root = Directory::NewRoot();
+
+ return true;
+}
+
+void
+ProxyDatabase::Close()
+{
+ assert(connection != nullptr);
+
+ root->Free();
+ mpd_connection_free(connection);
+}
+
+static Song *
+Convert(const struct mpd_song *song);
+
+Song *
+ProxyDatabase::GetSong(const char *uri, GError **error_r) const
+{
+ // TODO: implement
+ // TODO: auto-reconnect
+
+ if (!mpd_send_list_meta(connection, uri)) {
+ CheckError(connection, error_r);
+ return nullptr;
+ }
+
+ struct mpd_song *song = mpd_recv_song(connection);
+ Song *song2 = song != nullptr
+ ? Convert(song)
+ : nullptr;
+ mpd_song_free(song);
+ if (!mpd_response_finish(connection)) {
+ if (song2 != nullptr)
+ song2->Free();
+
+ CheckError(connection, error_r);
+ return nullptr;
+ }
+
+ if (song2 == nullptr)
+ g_set_error(error_r, db_quark(), DB_NOT_FOUND,
+ "No such song: %s", uri);
+
+ return song2;
+}
+
+void
+ProxyDatabase::ReturnSong(Song *song) const
+{
+ assert(song != nullptr);
+ assert(song->IsInDatabase());
+ assert(song->IsDetached());
+
+ song->Free();
+}
+
+static bool
+Visit(struct mpd_connection *connection, const char *uri,
+ bool recursive, VisitDirectory visit_directory, VisitSong visit_song,
+ VisitPlaylist visit_playlist, GError **error_r);
+
+static bool
+Visit(struct mpd_connection *connection,
+ bool recursive, const struct mpd_directory *directory,
+ VisitDirectory visit_directory, VisitSong visit_song,
+ VisitPlaylist visit_playlist, GError **error_r)
+{
+ const char *path = mpd_directory_get_path(directory);
+
+ if (visit_directory) {
+ Directory *d = Directory::NewGeneric(path, &detached_root);
+ bool success = visit_directory(*d, error_r);
+ d->Free();
+ if (!success)
+ return false;
+ }
+
+ if (recursive &&
+ !Visit(connection, path, recursive,
+ visit_directory, visit_song, visit_playlist, error_r))
+ return false;
+
+ return true;
+}
+
+static void
+Copy(Tag &tag, enum tag_type d_tag,
+ const struct mpd_song *song, enum mpd_tag_type s_tag)
+{
+
+ for (unsigned i = 0;; ++i) {
+ const char *value = mpd_song_get_tag(song, s_tag, i);
+ if (value == NULL)
+ break;
+
+ tag.AddItem(d_tag, value);
+ }
+}
+
+static Song *
+Convert(const struct mpd_song *song)
+{
+ Song *s = Song::NewDetached(mpd_song_get_uri(song));
+
+ s->mtime = mpd_song_get_last_modified(song);
+ s->start_ms = mpd_song_get_start(song) * 1000;
+ s->end_ms = mpd_song_get_end(song) * 1000;
+
+ Tag *tag = new Tag();
+ tag->time = mpd_song_get_duration(song);
+
+ tag->BeginAdd();
+ for (const auto *i = &tag_table[0]; i->d != TAG_NUM_OF_ITEM_TYPES; ++i)
+ Copy(*tag, i->d, song, i->s);
+ tag->EndAdd();
+
+ s->tag = tag;
+
+ return s;
+}
+
+static bool
+Visit(const struct mpd_song *song,
+ VisitSong visit_song, GError **error_r)
+{
+ if (!visit_song)
+ return true;
+
+ Song *s = Convert(song);
+ bool success = visit_song(*s, error_r);
+ s->Free();
+
+ return success;
+}
+
+static bool
+Visit(const struct mpd_playlist *playlist,
+ VisitPlaylist visit_playlist, GError **error_r)
+{
+ if (!visit_playlist)
+ return true;
+
+ PlaylistInfo p(mpd_playlist_get_path(playlist),
+ mpd_playlist_get_last_modified(playlist));
+
+ return visit_playlist(p, detached_root, error_r);
+}
+
+class ProxyEntity {
+ struct mpd_entity *entity;
+
+public:
+ explicit ProxyEntity(struct mpd_entity *_entity)
+ :entity(_entity) {}
+
+ ProxyEntity(const ProxyEntity &other) = delete;
+
+ ProxyEntity(ProxyEntity &&other)
+ :entity(other.entity) {
+ other.entity = nullptr;
+ }
+
+ ~ProxyEntity() {
+ if (entity != nullptr)
+ mpd_entity_free(entity);
+ }
+
+ ProxyEntity &operator=(const ProxyEntity &other) = delete;
+
+ operator const struct mpd_entity *() const {
+ return entity;
+ }
+};
+
+static std::list<ProxyEntity>
+ReceiveEntities(struct mpd_connection *connection)
+{
+ std::list<ProxyEntity> entities;
+ struct mpd_entity *entity;
+ while ((entity = mpd_recv_entity(connection)) != NULL)
+ entities.push_back(ProxyEntity(entity));
+
+ mpd_response_finish(connection);
+ return entities;
+}
+
+static bool
+Visit(struct mpd_connection *connection, const char *uri,
+ bool recursive, VisitDirectory visit_directory, VisitSong visit_song,
+ VisitPlaylist visit_playlist, GError **error_r)
+{
+ if (!mpd_send_list_meta(connection, uri))
+ return CheckError(connection, error_r);
+
+ std::list<ProxyEntity> entities(ReceiveEntities(connection));
+ if (!CheckError(connection, error_r))
+ return false;
+
+ for (const auto &entity : entities) {
+ switch (mpd_entity_get_type(entity)) {
+ case MPD_ENTITY_TYPE_UNKNOWN:
+ break;
+
+ case MPD_ENTITY_TYPE_DIRECTORY:
+ if (!Visit(connection, recursive,
+ mpd_entity_get_directory(entity),
+ visit_directory, visit_song, visit_playlist,
+ error_r))
+ return false;
+ break;
+
+ case MPD_ENTITY_TYPE_SONG:
+ if (!Visit(mpd_entity_get_song(entity), visit_song,
+ error_r))
+ return false;
+ break;
+
+ case MPD_ENTITY_TYPE_PLAYLIST:
+ if (!Visit(mpd_entity_get_playlist(entity),
+ visit_playlist, error_r))
+ return false;
+ break;
+ }
+ }
+
+ return CheckError(connection, error_r);
+}
+
+bool
+ProxyDatabase::Visit(const DatabaseSelection &selection,
+ VisitDirectory visit_directory,
+ VisitSong visit_song,
+ VisitPlaylist visit_playlist,
+ GError **error_r) const
+{
+ // TODO: match
+ // TODO: auto-reconnect
+
+ return ::Visit(connection, selection.uri, selection.recursive,
+ visit_directory, visit_song, visit_playlist,
+ error_r);
+}
+
+bool
+ProxyDatabase::VisitUniqueTags(const DatabaseSelection &selection,
+ enum tag_type tag_type,
+ VisitString visit_string,
+ GError **error_r) const
+{
+ enum mpd_tag_type tag_type2 = Convert(tag_type);
+ if (tag_type2 == MPD_TAG_COUNT) {
+ g_set_error_literal(error_r, libmpdclient_quark(), 0,
+ "Unsupported tag");
+ return false;
+ }
+
+ if (!mpd_search_db_tags(connection, tag_type2))
+ return CheckError(connection, error_r);
+
+ // TODO: match
+ (void)selection;
+
+ if (!mpd_search_commit(connection))
+ return CheckError(connection, error_r);
+
+ bool result = true;
+
+ struct mpd_pair *pair;
+ while (result &&
+ (pair = mpd_recv_pair_tag(connection, tag_type2)) != nullptr) {
+ result = visit_string(pair->value, error_r);
+ mpd_return_pair(connection, pair);
+ }
+
+ return mpd_response_finish(connection) &&
+ CheckError(connection, error_r) &&
+ result;
+}
+
+bool
+ProxyDatabase::GetStats(const DatabaseSelection &selection,
+ DatabaseStats &stats, GError **error_r) const
+{
+ // TODO: match
+ (void)selection;
+
+ struct mpd_stats *stats2 =
+ mpd_run_stats(connection);
+ if (stats2 == nullptr)
+ return CheckError(connection, error_r);
+
+ stats.song_count = mpd_stats_get_number_of_songs(stats2);
+ stats.total_duration = mpd_stats_get_db_play_time(stats2);
+ stats.artist_count = mpd_stats_get_number_of_artists(stats2);
+ stats.album_count = mpd_stats_get_number_of_albums(stats2);
+ mpd_stats_free(stats2);
+
+ return true;
+}
+
+const DatabasePlugin proxy_db_plugin = {
+ "proxy",
+ ProxyDatabase::Create,
+};
diff --git a/src/db/ProxyDatabasePlugin.hxx b/src/db/ProxyDatabasePlugin.hxx
new file mode 100644
index 000000000..8e878baca
--- /dev/null
+++ b/src/db/ProxyDatabasePlugin.hxx
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+#ifndef MPD_PROXY_DATABASE_PLUGIN_HXX
+#define MPD_PROXY_DATABASE_PLUGIN_HXX
+
+struct DatabasePlugin;
+
+extern const DatabasePlugin proxy_db_plugin;
+
+#endif
diff --git a/src/db/SimpleDatabasePlugin.cxx b/src/db/SimpleDatabasePlugin.cxx
new file mode 100644
index 000000000..ac5a75438
--- /dev/null
+++ b/src/db/SimpleDatabasePlugin.cxx
@@ -0,0 +1,349 @@
+/*
+ * 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.
+ */
+
+#include "config.h"
+#include "SimpleDatabasePlugin.hxx"
+#include "DatabaseSelection.hxx"
+#include "DatabaseHelpers.hxx"
+#include "Directory.hxx"
+#include "SongFilter.hxx"
+#include "DatabaseSave.hxx"
+#include "DatabaseLock.hxx"
+#include "db_error.h"
+#include "TextFile.hxx"
+#include "conf.h"
+#include "fs/FileSystem.hxx"
+
+#include <sys/types.h>
+#include <errno.h>
+
+G_GNUC_CONST
+static inline GQuark
+simple_db_quark(void)
+{
+ return g_quark_from_static_string("simple_db");
+}
+
+Database *
+SimpleDatabase::Create(const config_param &param, GError **error_r)
+{
+ SimpleDatabase *db = new SimpleDatabase();
+ if (!db->Configure(param, error_r)) {
+ delete db;
+ db = NULL;
+ }
+
+ return db;
+}
+
+bool
+SimpleDatabase::Configure(const config_param &param, GError **error_r)
+{
+ GError *error = NULL;
+
+ char *_path = param.DupBlockPath("path", &error);
+ if (_path == NULL) {
+ if (error != NULL)
+ g_propagate_error(error_r, error);
+ else
+ g_set_error(error_r, simple_db_quark(), 0,
+ "No \"path\" parameter specified");
+ return false;
+ }
+
+ path = Path::FromUTF8(_path);
+ path_utf8 = _path;
+
+ free(_path);
+
+ if (path.IsNull()) {
+ g_set_error(error_r, simple_db_quark(), 0,
+ "Failed to convert database path to FS encoding");
+ return false;
+ }
+
+ return true;
+}
+
+bool
+SimpleDatabase::Check(GError **error_r) const
+{
+ assert(!path.IsNull());
+ assert(!path.empty());
+
+ /* Check if the file exists */
+ if (!CheckAccess(path, F_OK)) {
+ /* 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 */
+ const Path dirPath = path.GetDirectoryName();
+
+ /* Check that the parent part of the path is a directory */
+ struct stat st;
+ if (!StatFile(dirPath, st)) {
+ g_set_error(error_r, simple_db_quark(), errno,
+ "Couldn't stat parent directory of db file "
+ "\"%s\": %s",
+ path_utf8.c_str(), g_strerror(errno));
+ return false;
+ }
+
+ if (!S_ISDIR(st.st_mode)) {
+ g_set_error(error_r, simple_db_quark(), 0,
+ "Couldn't create db file \"%s\" because the "
+ "parent path is not a directory",
+ path_utf8.c_str());
+ return false;
+ }
+
+ /* Check if we can write to the directory */
+ if (!CheckAccess(dirPath, X_OK | W_OK)) {
+ int error = errno;
+ const std::string dirPath_utf8 = dirPath.ToUTF8();
+ g_set_error(error_r, simple_db_quark(), error,
+ "Can't create db file in \"%s\": %s",
+ dirPath_utf8.c_str(), g_strerror(error));
+ return false;
+ }
+
+ return true;
+ }
+
+ /* Path exists, now check if it's a regular file */
+ struct stat st;
+ if (!StatFile(path, st)) {
+ g_set_error(error_r, simple_db_quark(), errno,
+ "Couldn't stat db file \"%s\": %s",
+ path_utf8.c_str(), g_strerror(errno));
+ return false;
+ }
+
+ if (!S_ISREG(st.st_mode)) {
+ g_set_error(error_r, simple_db_quark(), 0,
+ "db file \"%s\" is not a regular file",
+ path_utf8.c_str());
+ return false;
+ }
+
+ /* And check that we can write to it */
+ if (!CheckAccess(path, R_OK | W_OK)) {
+ g_set_error(error_r, simple_db_quark(), errno,
+ "Can't open db file \"%s\" for reading/writing: %s",
+ path_utf8.c_str(), g_strerror(errno));
+ return false;
+ }
+
+ return true;
+}
+
+bool
+SimpleDatabase::Load(GError **error_r)
+{
+ assert(!path.empty());
+ assert(root != NULL);
+
+ TextFile file(path);
+ if (file.HasFailed()) {
+ g_set_error(error_r, simple_db_quark(), errno,
+ "Failed to open database file \"%s\": %s",
+ path_utf8.c_str(), g_strerror(errno));
+ return false;
+ }
+
+ if (!db_load_internal(file, root, error_r))
+ return false;
+
+ struct stat st;
+ if (StatFile(path, st))
+ mtime = st.st_mtime;
+
+ return true;
+}
+
+bool
+SimpleDatabase::Open(GError **error_r)
+{
+ root = Directory::NewRoot();
+ mtime = 0;
+
+#ifndef NDEBUG
+ borrowed_song_count = 0;
+#endif
+
+ GError *error = NULL;
+ if (!Load(&error)) {
+ root->Free();
+
+ g_warning("Failed to load database: %s", error->message);
+ g_error_free(error);
+
+ if (!Check(error_r))
+ return false;
+
+ root = Directory::NewRoot();
+ }
+
+ return true;
+}
+
+void
+SimpleDatabase::Close()
+{
+ assert(root != NULL);
+ assert(borrowed_song_count == 0);
+
+ root->Free();
+}
+
+Song *
+SimpleDatabase::GetSong(const char *uri, GError **error_r) const
+{
+ assert(root != NULL);
+
+ db_lock();
+ Song *song = root->LookupSong(uri);
+ db_unlock();
+ if (song == NULL)
+ g_set_error(error_r, db_quark(), DB_NOT_FOUND,
+ "No such song: %s", uri);
+#ifndef NDEBUG
+ else
+ ++const_cast<unsigned &>(borrowed_song_count);
+#endif
+
+ return song;
+}
+
+void
+SimpleDatabase::ReturnSong(gcc_unused Song *song) const
+{
+ assert(song != nullptr);
+
+#ifndef NDEBUG
+ assert(borrowed_song_count > 0);
+ --const_cast<unsigned &>(borrowed_song_count);
+#endif
+}
+
+G_GNUC_PURE
+const Directory *
+SimpleDatabase::LookupDirectory(const char *uri) const
+{
+ assert(root != NULL);
+ assert(uri != NULL);
+
+ ScopeDatabaseLock protect;
+ return root->LookupDirectory(uri);
+}
+
+bool
+SimpleDatabase::Visit(const DatabaseSelection &selection,
+ VisitDirectory visit_directory,
+ VisitSong visit_song,
+ VisitPlaylist visit_playlist,
+ GError **error_r) const
+{
+ ScopeDatabaseLock protect;
+
+ const Directory *directory = root->LookupDirectory(selection.uri);
+ if (directory == NULL) {
+ if (visit_song) {
+ Song *song = root->LookupSong(selection.uri);
+ if (song != nullptr)
+ return !selection.Match(*song) ||
+ visit_song(*song, error_r);
+ }
+
+ g_set_error(error_r, db_quark(), DB_NOT_FOUND,
+ "No such directory");
+ return false;
+ }
+
+ if (selection.recursive && visit_directory &&
+ !visit_directory(*directory, error_r))
+ return false;
+
+ return directory->Walk(selection.recursive, selection.filter,
+ visit_directory, visit_song, visit_playlist,
+ error_r);
+}
+
+bool
+SimpleDatabase::VisitUniqueTags(const DatabaseSelection &selection,
+ enum tag_type tag_type,
+ VisitString visit_string,
+ GError **error_r) const
+{
+ return ::VisitUniqueTags(*this, selection, tag_type, visit_string,
+ error_r);
+}
+
+bool
+SimpleDatabase::GetStats(const DatabaseSelection &selection,
+ DatabaseStats &stats, GError **error_r) const
+{
+ return ::GetStats(*this, selection, stats, error_r);
+}
+
+bool
+SimpleDatabase::Save(GError **error_r)
+{
+ db_lock();
+
+ g_debug("removing empty directories from DB");
+ root->PruneEmpty();
+
+ g_debug("sorting DB");
+ root->Sort();
+
+ db_unlock();
+
+ g_debug("writing DB");
+
+ FILE *fp = FOpen(path, FOpenMode::WriteText);
+ if (!fp) {
+ g_set_error(error_r, simple_db_quark(), errno,
+ "unable to write to db file \"%s\": %s",
+ path_utf8.c_str(), g_strerror(errno));
+ return false;
+ }
+
+ db_save_internal(fp, root);
+
+ if (ferror(fp)) {
+ g_set_error(error_r, simple_db_quark(), errno,
+ "Failed to write to database file: %s",
+ g_strerror(errno));
+ fclose(fp);
+ return false;
+ }
+
+ fclose(fp);
+
+ struct stat st;
+ if (StatFile(path, st))
+ mtime = st.st_mtime;
+
+ return true;
+}
+
+const DatabasePlugin simple_db_plugin = {
+ "simple",
+ SimpleDatabase::Create,
+};
diff --git a/src/db/SimpleDatabasePlugin.hxx b/src/db/SimpleDatabasePlugin.hxx
new file mode 100644
index 000000000..7250ea063
--- /dev/null
+++ b/src/db/SimpleDatabasePlugin.hxx
@@ -0,0 +1,102 @@
+/*
+ * 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_SIMPLE_DATABASE_PLUGIN_HXX
+#define MPD_SIMPLE_DATABASE_PLUGIN_HXX
+
+#include "DatabasePlugin.hxx"
+#include "fs/Path.hxx"
+#include "gcc.h"
+
+#include <cassert>
+
+#include <time.h>
+
+struct Directory;
+
+class SimpleDatabase : public Database {
+ Path path;
+ std::string path_utf8;
+
+ Directory *root;
+
+ time_t mtime;
+
+#ifndef NDEBUG
+ unsigned borrowed_song_count;
+#endif
+
+ SimpleDatabase()
+ :path(Path::Null()) {}
+
+public:
+ gcc_pure
+ Directory *GetRoot() {
+ assert(root != NULL);
+
+ return root;
+ }
+
+ bool Save(GError **error_r);
+
+ gcc_pure
+ time_t GetLastModified() const {
+ return mtime;
+ }
+
+ static Database *Create(const config_param &param,
+ GError **error_r);
+
+ virtual bool Open(GError **error_r) override;
+ virtual void Close() override;
+
+ virtual Song *GetSong(const char *uri_utf8,
+ GError **error_r) const override;
+ virtual void ReturnSong(Song *song) const;
+
+ virtual bool Visit(const DatabaseSelection &selection,
+ VisitDirectory visit_directory,
+ VisitSong visit_song,
+ VisitPlaylist visit_playlist,
+ GError **error_r) const override;
+
+ virtual bool VisitUniqueTags(const DatabaseSelection &selection,
+ enum tag_type tag_type,
+ VisitString visit_string,
+ GError **error_r) const override;
+
+ virtual bool GetStats(const DatabaseSelection &selection,
+ DatabaseStats &stats,
+ GError **error_r) const override;
+
+protected:
+ bool Configure(const config_param &param, GError **error_r);
+
+ gcc_pure
+ bool Check(GError **error_r) const;
+
+ bool Load(GError **error_r);
+
+ gcc_pure
+ const Directory *LookupDirectory(const char *uri) const;
+};
+
+extern const DatabasePlugin simple_db_plugin;
+
+#endif
diff --git a/src/db/simple_db_plugin.c b/src/db/simple_db_plugin.c
deleted file mode 100644
index 697e8da5f..000000000
--- a/src/db/simple_db_plugin.c
+++ /dev/null
@@ -1,357 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "simple_db_plugin.h"
-#include "db_internal.h"
-#include "db_error.h"
-#include "db_selection.h"
-#include "db_visitor.h"
-#include "db_save.h"
-#include "db_lock.h"
-#include "conf.h"
-#include "directory.h"
-
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <errno.h>
-
-struct simple_db {
- struct db base;
-
- char *path;
-
- struct directory *root;
-
- time_t mtime;
-};
-
-G_GNUC_CONST
-static inline GQuark
-simple_db_quark(void)
-{
- return g_quark_from_static_string("simple_db");
-}
-
-G_GNUC_PURE
-static const struct directory *
-simple_db_lookup_directory(const struct simple_db *db, const char *uri)
-{
- assert(db != NULL);
- assert(db->root != NULL);
- assert(uri != NULL);
-
- db_lock();
- struct directory *directory =
- directory_lookup_directory(db->root, uri);
- db_unlock();
- return directory;
-}
-
-static struct db *
-simple_db_init(const struct config_param *param, GError **error_r)
-{
- struct simple_db *db = g_malloc(sizeof(*db));
- db_base_init(&db->base, &simple_db_plugin);
-
- GError *error = NULL;
- db->path = config_dup_block_path(param, "path", &error);
- if (db->path == NULL) {
- g_free(db);
- if (error != NULL)
- g_propagate_error(error_r, error);
- else
- g_set_error(error_r, simple_db_quark(), 0,
- "No \"path\" parameter specified");
- return NULL;
- }
-
- return &db->base;
-}
-
-static void
-simple_db_finish(struct db *_db)
-{
- struct simple_db *db = (struct simple_db *)_db;
-
- g_free(db->path);
- g_free(db);
-}
-
-static bool
-simple_db_check(struct simple_db *db, GError **error_r)
-{
- assert(db != NULL);
- assert(db->path != NULL);
-
- /* Check if the file exists */
- if (access(db->path, F_OK)) {
- /* 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 */
- char *dirPath = g_path_get_dirname(db->path);
-
- /* Check that the parent part of the path is a directory */
- struct stat st;
- if (stat(dirPath, &st) < 0) {
- g_free(dirPath);
- g_set_error(error_r, simple_db_quark(), errno,
- "Couldn't stat parent directory of db file "
- "\"%s\": %s",
- db->path, g_strerror(errno));
- return false;
- }
-
- if (!S_ISDIR(st.st_mode)) {
- g_free(dirPath);
- g_set_error(error_r, simple_db_quark(), 0,
- "Couldn't create db file \"%s\" because the "
- "parent path is not a directory",
- db->path);
- return false;
- }
-
- /* Check if we can write to the directory */
- if (access(dirPath, X_OK | W_OK)) {
- g_set_error(error_r, simple_db_quark(), errno,
- "Can't create db file in \"%s\": %s",
- dirPath, g_strerror(errno));
- g_free(dirPath);
- return false;
- }
-
- g_free(dirPath);
-
- return true;
- }
-
- /* Path exists, now check if it's a regular file */
- struct stat st;
- if (stat(db->path, &st) < 0) {
- g_set_error(error_r, simple_db_quark(), errno,
- "Couldn't stat db file \"%s\": %s",
- db->path, g_strerror(errno));
- return false;
- }
-
- if (!S_ISREG(st.st_mode)) {
- g_set_error(error_r, simple_db_quark(), 0,
- "db file \"%s\" is not a regular file",
- db->path);
- return false;
- }
-
- /* And check that we can write to it */
- if (access(db->path, R_OK | W_OK)) {
- g_set_error(error_r, simple_db_quark(), errno,
- "Can't open db file \"%s\" for reading/writing: %s",
- db->path, g_strerror(errno));
- return false;
- }
-
- return true;
-}
-
-static bool
-simple_db_load(struct simple_db *db, GError **error_r)
-{
- assert(db != NULL);
- assert(db->path != NULL);
- assert(db->root != NULL);
-
- FILE *fp = fopen(db->path, "r");
- if (fp == NULL) {
- g_set_error(error_r, simple_db_quark(), errno,
- "Failed to open database file \"%s\": %s",
- db->path, g_strerror(errno));
- return false;
- }
-
- if (!db_load_internal(fp, db->root, error_r)) {
- fclose(fp);
- return false;
- }
-
- fclose(fp);
-
- struct stat st;
- if (stat(db->path, &st) == 0)
- db->mtime = st.st_mtime;
-
- return true;
-}
-
-static bool
-simple_db_open(struct db *_db, G_GNUC_UNUSED GError **error_r)
-{
- struct simple_db *db = (struct simple_db *)_db;
-
- db->root = directory_new_root();
- db->mtime = 0;
-
- GError *error = NULL;
- if (!simple_db_load(db, &error)) {
- directory_free(db->root);
-
- g_warning("Failed to load database: %s", error->message);
- g_error_free(error);
-
- if (!simple_db_check(db, error_r))
- return false;
-
- db->root = directory_new_root();
- }
-
- return true;
-}
-
-static void
-simple_db_close(struct db *_db)
-{
- struct simple_db *db = (struct simple_db *)_db;
-
- assert(db->root != NULL);
-
- directory_free(db->root);
-}
-
-static struct song *
-simple_db_get_song(struct db *_db, const char *uri, GError **error_r)
-{
- struct simple_db *db = (struct simple_db *)_db;
-
- assert(db->root != NULL);
-
- db_lock();
- struct song *song = directory_lookup_song(db->root, uri);
- db_unlock();
- if (song == NULL)
- g_set_error(error_r, db_quark(), DB_NOT_FOUND,
- "No such song: %s", uri);
-
- return song;
-}
-
-static bool
-simple_db_visit(struct db *_db, const struct db_selection *selection,
- const struct db_visitor *visitor, void *ctx,
- GError **error_r)
-{
- const struct simple_db *db = (const struct simple_db *)_db;
- const struct directory *directory =
- simple_db_lookup_directory(db, selection->uri);
- if (directory == NULL) {
- struct song *song;
- if (visitor->song != NULL &&
- (song = simple_db_get_song(_db, selection->uri, NULL)) != NULL)
- return visitor->song(song, ctx, error_r);
-
- g_set_error(error_r, db_quark(), DB_NOT_FOUND,
- "No such directory");
- return false;
- }
-
- if (selection->recursive && visitor->directory != NULL &&
- !visitor->directory(directory, ctx, error_r))
- return false;
-
- db_lock();
- bool ret = directory_walk(directory, selection->recursive,
- visitor, ctx, error_r);
- db_unlock();
- return ret;
-}
-
-const struct db_plugin simple_db_plugin = {
- .name = "simple",
- .init = simple_db_init,
- .finish = simple_db_finish,
- .open = simple_db_open,
- .close = simple_db_close,
- .get_song = simple_db_get_song,
- .visit = simple_db_visit,
-};
-
-struct directory *
-simple_db_get_root(struct db *_db)
-{
- struct simple_db *db = (struct simple_db *)_db;
-
- assert(db != NULL);
- assert(db->root != NULL);
-
- return db->root;
-}
-
-bool
-simple_db_save(struct db *_db, GError **error_r)
-{
- struct simple_db *db = (struct simple_db *)_db;
- struct directory *music_root = db->root;
-
- db_lock();
-
- g_debug("removing empty directories from DB");
- directory_prune_empty(music_root);
-
- g_debug("sorting DB");
- directory_sort(music_root);
-
- db_unlock();
-
- g_debug("writing DB");
-
- FILE *fp = fopen(db->path, "w");
- if (!fp) {
- g_set_error(error_r, simple_db_quark(), errno,
- "unable to write to db file \"%s\": %s",
- db->path, g_strerror(errno));
- return false;
- }
-
- db_save_internal(fp, music_root);
-
- if (ferror(fp)) {
- g_set_error(error_r, simple_db_quark(), errno,
- "Failed to write to database file: %s",
- g_strerror(errno));
- fclose(fp);
- return false;
- }
-
- fclose(fp);
-
- struct stat st;
- if (stat(db->path, &st) == 0)
- db->mtime = st.st_mtime;
-
- return true;
-}
-
-time_t
-simple_db_get_mtime(const struct db *_db)
-{
- const struct simple_db *db = (const struct simple_db *)_db;
-
- assert(db != NULL);
- assert(db->root != NULL);
-
- return db->mtime;
-}
diff --git a/src/db/simple_db_plugin.h b/src/db/simple_db_plugin.h
deleted file mode 100644
index 511505846..000000000
--- a/src/db/simple_db_plugin.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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_SIMPLE_DB_PLUGIN_H
-#define MPD_SIMPLE_DB_PLUGIN_H
-
-#include <glib.h>
-#include <stdbool.h>
-#include <time.h>
-
-extern const struct db_plugin simple_db_plugin;
-
-struct db;
-
-G_GNUC_PURE
-struct directory *
-simple_db_get_root(struct db *db);
-
-bool
-simple_db_save(struct db *db, GError **error_r);
-
-G_GNUC_PURE
-time_t
-simple_db_get_mtime(const struct db *db);
-
-#endif
diff --git a/src/dbUtils.c b/src/dbUtils.c
deleted file mode 100644
index c212d9f9c..000000000
--- a/src/dbUtils.c
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "dbUtils.h"
-#include "locate.h"
-#include "database.h"
-#include "db_visitor.h"
-#include "playlist.h"
-#include "stored_playlist.h"
-
-#include <glib.h>
-
-static bool
-add_to_queue_song(struct song *song, void *ctx, GError **error_r)
-{
- struct player_control *pc = ctx;
-
- enum playlist_result result =
- playlist_append_song(&g_playlist, pc, song, NULL);
- if (result != PLAYLIST_RESULT_SUCCESS) {
- g_set_error(error_r, playlist_quark(), result,
- "Playlist error");
- return false;
- }
-
- return true;
-}
-
-static const struct db_visitor add_to_queue_visitor = {
- .song = add_to_queue_song,
-};
-
-bool
-addAllIn(struct player_control *pc, const char *uri, GError **error_r)
-{
- return db_walk(uri, &add_to_queue_visitor, pc, error_r);
-}
-
-struct add_data {
- const char *path;
-};
-
-static bool
-add_to_spl_song(struct song *song, void *ctx, GError **error_r)
-{
- struct add_data *data = ctx;
-
- if (!spl_append_song(data->path, song, error_r))
- return false;
-
- return true;
-}
-
-static const struct db_visitor add_to_spl_visitor = {
- .song = add_to_spl_song,
-};
-
-bool
-addAllInToStoredPlaylist(const char *uri_utf8, const char *path_utf8,
- GError **error_r)
-{
- struct add_data data = {
- .path = path_utf8,
- };
-
- return db_walk(uri_utf8, &add_to_spl_visitor, &data, error_r);
-}
-
-struct find_add_data {
- struct player_control *pc;
- const struct locate_item_list *criteria;
-};
-
-static bool
-find_add_song(struct song *song, void *ctx, GError **error_r)
-{
- struct find_add_data *data = ctx;
-
- if (!locate_song_match(song, data->criteria))
- return true;
-
- enum playlist_result result =
- playlist_append_song(&g_playlist, data->pc,
- song, NULL);
- if (result != PLAYLIST_RESULT_SUCCESS) {
- g_set_error(error_r, playlist_quark(), result,
- "Playlist error");
- return false;
- }
-
- return true;
-}
-
-static const struct db_visitor find_add_visitor = {
- .song = find_add_song,
-};
-
-bool
-findAddIn(struct player_control *pc, const char *name,
- const struct locate_item_list *criteria, GError **error_r)
-{
- struct find_add_data data;
- data.pc = pc;
- data.criteria = criteria;
-
- return db_walk(name, &find_add_visitor, &data, error_r);
-}
-
-static bool
-searchadd_visitor_song(struct song *song, void *_data, GError **error_r)
-{
- struct find_add_data *data = _data;
-
- if (!locate_song_search(song, data->criteria))
- return true;
-
- enum playlist_result result =
- playlist_append_song(&g_playlist, data->pc, song, NULL);
- if (result != PLAYLIST_RESULT_SUCCESS) {
- g_set_error(error_r, playlist_quark(), result,
- "Playlist error");
- return false;
- }
-
- return true;
-}
-
-static const struct db_visitor searchadd_visitor = {
- .song = searchadd_visitor_song,
-};
-
-bool
-search_add_songs(struct player_control *pc, const char *uri,
- const struct locate_item_list *criteria,
- GError **error_r)
-{
- struct locate_item_list *new_list =
- locate_item_list_casefold(criteria);
- struct find_add_data data = {
- .pc = pc,
- .criteria = new_list,
- };
-
- bool success = db_walk(uri, &searchadd_visitor, &data, error_r);
-
- locate_item_list_free(new_list);
-
- return success;
-}
-
-struct search_add_playlist_data {
- const char *playlist;
- const struct locate_item_list *criteria;
-};
-
-static bool
-searchaddpl_visitor_song(struct song *song, void *_data,
- G_GNUC_UNUSED GError **error_r)
-{
- struct search_add_playlist_data *data = _data;
-
- if (!locate_song_search(song, data->criteria))
- return true;
-
- if (!spl_append_song(data->playlist, song, error_r))
- return false;
-
- return true;
-}
-
-static const struct db_visitor searchaddpl_visitor = {
- .song = searchaddpl_visitor_song,
-};
-
-bool
-search_add_to_playlist(const char *uri, const char *path_utf8,
- const struct locate_item_list *criteria,
- GError **error_r)
-{
- struct locate_item_list *new_list
- = locate_item_list_casefold(criteria);
- struct search_add_playlist_data data = {
- .playlist = path_utf8,
- .criteria = new_list,
- };
-
- bool success = db_walk(uri, &searchaddpl_visitor, &data, error_r);
-
- locate_item_list_free(new_list);
-
- return success;
-}
diff --git a/src/dbUtils.h b/src/dbUtils.h
deleted file mode 100644
index 706c807fd..000000000
--- a/src/dbUtils.h
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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_DB_UTILS_H
-#define MPD_DB_UTILS_H
-
-#include "gcc.h"
-
-#include <glib.h>
-#include <stdbool.h>
-
-struct locate_item_list;
-struct player_control;
-
-gcc_nonnull(1,2)
-bool
-addAllIn(struct player_control *pc, const char *uri, GError **error_r);
-
-gcc_nonnull(1,2)
-bool
-addAllInToStoredPlaylist(const char *uri_utf8, const char *path_utf8,
- GError **error_r);
-
-gcc_nonnull(1,2,3)
-bool
-findAddIn(struct player_control *pc, const char *name,
- const struct locate_item_list *criteria, GError **error_r);
-
-gcc_nonnull(1,2,3)
-bool
-search_add_songs(struct player_control *pc, const char *uri,
- const struct locate_item_list *criteria, GError **error_r);
-
-gcc_nonnull(1,2,3)
-bool
-search_add_to_playlist(const char *uri, const char *path_utf8,
- const struct locate_item_list *criteria,
- GError **error_r);
-
-#endif
diff --git a/src/db_internal.h b/src/db_internal.h
deleted file mode 100644
index a33351524..000000000
--- a/src/db_internal.h
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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_DB_INTERNAL_H
-#define MPD_DB_INTERNAL_H
-
-#include "db_plugin.h"
-
-#include <assert.h>
-
-static inline void
-db_base_init(struct db *db, const struct db_plugin *plugin)
-{
- assert(plugin != NULL);
-
- db->plugin = plugin;
-}
-
-#endif
diff --git a/src/db_lock.c b/src/db_lock.c
deleted file mode 100644
index 53759673d..000000000
--- a/src/db_lock.c
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "db_lock.h"
-#include "gcc.h"
-
-#if GCC_CHECK_VERSION(4, 2)
-/* workaround for a warning caused by G_STATIC_MUTEX_INIT */
-#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
-#endif
-
-GStaticMutex db_mutex = G_STATIC_MUTEX_INIT;
-
-#ifndef NDEBUG
-GThread *db_mutex_holder;
-#endif
diff --git a/src/db_lock.h b/src/db_lock.h
deleted file mode 100644
index 4640502f3..000000000
--- a/src/db_lock.h
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * 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.
- */
-
-/** \file
- *
- * Support for locking data structures from the database, for safe
- * multi-threading.
- */
-
-#ifndef MPD_DB_LOCK_H
-#define MPD_DB_LOCK_H
-
-#include "check.h"
-
-#include <glib.h>
-#include <assert.h>
-#include <stdbool.h>
-
-extern GStaticMutex db_mutex;
-
-#ifndef NDEBUG
-
-extern GThread *db_mutex_holder;
-
-/**
- * Does the current thread hold the database lock?
- */
-G_GNUC_PURE
-static inline bool
-holding_db_lock(void)
-{
- return db_mutex_holder == g_thread_self();
-}
-
-#endif
-
-/**
- * Obtain the global database lock. This is needed before
- * dereferencing a #song or #directory. It is not recursive.
- */
-static inline void
-db_lock(void)
-{
- assert(!holding_db_lock());
-
- g_static_mutex_lock(&db_mutex);
-
- assert(db_mutex_holder == NULL);
-#ifndef NDEBUG
- db_mutex_holder = g_thread_self();
-#endif
-}
-
-/**
- * Release the global database lock.
- */
-static inline void
-db_unlock(void)
-{
- assert(holding_db_lock());
-#ifndef NDEBUG
- db_mutex_holder = NULL;
-#endif
-
- g_static_mutex_unlock(&db_mutex);
-}
-
-#endif
diff --git a/src/db_plugin.h b/src/db_plugin.h
deleted file mode 100644
index 1c7e14ede..000000000
--- a/src/db_plugin.h
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * 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.
- */
-
-/** \file
- *
- * This header declares the db_plugin class. It describes a
- * plugin API for databases of song metadata.
- */
-
-#ifndef MPD_DB_PLUGIN_H
-#define MPD_DB_PLUGIN_H
-
-#include <glib.h>
-#include <assert.h>
-#include <stdbool.h>
-
-struct config_param;
-struct db_selection;
-struct db_visitor;
-
-struct db {
- const struct db_plugin *plugin;
-};
-
-struct db_plugin {
- const char *name;
-
- /**
- * Allocates and configures a database.
- */
- struct db *(*init)(const struct config_param *param, GError **error_r);
-
- /**
- * Free instance data.
- */
- void (*finish)(struct db *db);
-
- /**
- * Open the database. Read it into memory if applicable.
- */
- bool (*open)(struct db *db, GError **error_r);
-
- /**
- * Close the database, free allocated memory.
- */
- void (*close)(struct db *db);
-
- /**
- * Look up a song (including tag data) in the database.
- *
- * @param the URI of the song within the music directory
- * (UTF-8)
- */
- struct song *(*get_song)(struct db *db, const char *uri,
- GError **error_r);
-
- /**
- * Visit the selected entities.
- */
- bool (*visit)(struct db *db, const struct db_selection *selection,
- const struct db_visitor *visitor, void *ctx,
- GError **error_r);
-};
-
-G_GNUC_MALLOC
-static inline struct db *
-db_plugin_new(const struct db_plugin *plugin, const struct config_param *param,
- GError **error_r)
-{
- assert(plugin != NULL);
- assert(plugin->init != NULL);
- assert(plugin->finish != NULL);
- assert(plugin->get_song != NULL);
- assert(plugin->visit != NULL);
- assert(error_r == NULL || *error_r == NULL);
-
- struct db *db = plugin->init(param, error_r);
- assert(db == NULL || db->plugin == plugin);
- assert(db != NULL || error_r == NULL || *error_r != NULL);
-
- return db;
-}
-
-static inline void
-db_plugin_free(struct db *db)
-{
- assert(db != NULL);
- assert(db->plugin != NULL);
- assert(db->plugin->finish != NULL);
-
- db->plugin->finish(db);
-}
-
-static inline bool
-db_plugin_open(struct db *db, GError **error_r)
-{
- assert(db != NULL);
- assert(db->plugin != NULL);
-
- return db->plugin->open != NULL
- ? db->plugin->open(db, error_r)
- : true;
-}
-
-static inline void
-db_plugin_close(struct db *db)
-{
- assert(db != NULL);
- assert(db->plugin != NULL);
-
- if (db->plugin->close != NULL)
- db->plugin->close(db);
-}
-
-static inline struct song *
-db_plugin_get_song(struct db *db, const char *uri, GError **error_r)
-{
- assert(db != NULL);
- assert(db->plugin != NULL);
- assert(db->plugin->get_song != NULL);
- assert(uri != NULL);
-
- return db->plugin->get_song(db, uri, error_r);
-}
-
-static inline bool
-db_plugin_visit(struct db *db, const struct db_selection *selection,
- const struct db_visitor *visitor, void *ctx,
- GError **error_r)
-{
- assert(db != NULL);
- assert(db->plugin != NULL);
- assert(selection != NULL);
- assert(visitor != NULL);
- assert(error_r == NULL || *error_r == NULL);
-
- return db->plugin->visit(db, selection, visitor, ctx, error_r);
-}
-
-#endif
diff --git a/src/db_print.c b/src/db_print.c
deleted file mode 100644
index 4d7e3f5ef..000000000
--- a/src/db_print.c
+++ /dev/null
@@ -1,393 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "db_print.h"
-#include "db_selection.h"
-#include "db_visitor.h"
-#include "locate.h"
-#include "directory.h"
-#include "database.h"
-#include "client.h"
-#include "song.h"
-#include "song_print.h"
-#include "playlist_vector.h"
-#include "tag.h"
-#include "strset.h"
-
-#include <glib.h>
-
-typedef struct _ListCommandItem {
- int8_t tagType;
- const struct locate_item_list *criteria;
-} ListCommandItem;
-
-typedef struct _SearchStats {
- const struct locate_item_list *criteria;
- int numberOfSongs;
- unsigned long playTime;
-} SearchStats;
-
-static bool
-print_visitor_directory(const struct directory *directory, void *data,
- G_GNUC_UNUSED GError **error_r)
-{
- struct client *client = data;
-
- if (!directory_is_root(directory))
- client_printf(client, "directory: %s\n", directory_get_path(directory));
-
- return true;
-}
-
-static void
-print_playlist_in_directory(struct client *client,
- const struct directory *directory,
- const char *name_utf8)
-{
- if (directory_is_root(directory))
- client_printf(client, "playlist: %s\n", name_utf8);
- else
- client_printf(client, "playlist: %s/%s\n",
- directory_get_path(directory), name_utf8);
-}
-
-static bool
-print_visitor_song(struct song *song, void *data,
- G_GNUC_UNUSED GError **error_r)
-{
- assert(song != NULL);
- assert(song->parent != NULL);
-
- struct client *client = data;
- song_print_uri(client, song);
-
- if (song->tag != NULL && song->tag->has_playlist)
- /* this song file has an embedded CUE sheet */
- print_playlist_in_directory(client, song->parent,
- song->uri);
-
- return true;
-}
-
-static bool
-print_visitor_song_info(struct song *song, void *data,
- G_GNUC_UNUSED GError **error_r)
-{
- assert(song != NULL);
- assert(song->parent != NULL);
-
- struct client *client = data;
- song_print_info(client, song);
-
- if (song->tag != NULL && song->tag->has_playlist)
- /* this song file has an embedded CUE sheet */
- print_playlist_in_directory(client, song->parent,
- song->uri);
-
- return true;
-}
-
-static bool
-print_visitor_playlist(const struct playlist_metadata *playlist,
- const struct directory *directory, void *ctx,
- G_GNUC_UNUSED GError **error_r)
-{
- struct client *client = ctx;
- print_playlist_in_directory(client, directory, playlist->name);
- return true;
-}
-
-static bool
-print_visitor_playlist_info(const struct playlist_metadata *playlist,
- const struct directory *directory,
- void *ctx, G_GNUC_UNUSED GError **error_r)
-{
- struct client *client = ctx;
- print_playlist_in_directory(client, directory, playlist->name);
-
-#ifndef G_OS_WIN32
- struct tm tm;
-#endif
- char timestamp[32];
- time_t t = playlist->mtime;
- strftime(timestamp, sizeof(timestamp),
-#ifdef G_OS_WIN32
- "%Y-%m-%dT%H:%M:%SZ",
- gmtime(&t)
-#else
- "%FT%TZ",
- gmtime_r(&t, &tm)
-#endif
- );
- client_printf(client, "Last-Modified: %s\n", timestamp);
-
- return true;
-}
-
-static const struct db_visitor print_visitor = {
- .directory = print_visitor_directory,
- .song = print_visitor_song,
- .playlist = print_visitor_playlist,
-};
-
-static const struct db_visitor print_info_visitor = {
- .directory = print_visitor_directory,
- .song = print_visitor_song_info,
- .playlist = print_visitor_playlist_info,
-};
-
-bool
-db_selection_print(struct client *client, const struct db_selection *selection,
- bool full, GError **error_r)
-{
- return db_visit(selection, full ? &print_info_visitor : &print_visitor,
- client, error_r);
-}
-
-struct search_data {
- struct client *client;
- const struct locate_item_list *criteria;
-};
-
-static bool
-search_visitor_song(struct song *song, void *_data,
- G_GNUC_UNUSED GError **error_r)
-{
- struct search_data *data = _data;
-
- if (locate_song_search(song, data->criteria))
- song_print_info(data->client, song);
-
- return true;
-}
-
-static const struct db_visitor search_visitor = {
- .song = search_visitor_song,
-};
-
-bool
-searchForSongsIn(struct client *client, const char *name,
- const struct locate_item_list *criteria,
- GError **error_r)
-{
- struct locate_item_list *new_list
- = locate_item_list_casefold(criteria);
- struct search_data data;
-
- data.client = client;
- data.criteria = new_list;
-
- bool success = db_walk(name, &search_visitor, &data, error_r);
-
- locate_item_list_free(new_list);
-
- return success;
-}
-
-static bool
-find_visitor_song(struct song *song, void *_data,
- G_GNUC_UNUSED GError **error_r)
-{
- struct search_data *data = _data;
-
- if (locate_song_match(song, data->criteria))
- song_print_info(data->client, song);
-
- return true;
-}
-
-static const struct db_visitor find_visitor = {
- .song = find_visitor_song,
-};
-
-bool
-findSongsIn(struct client *client, const char *name,
- const struct locate_item_list *criteria,
- GError **error_r)
-{
- struct search_data data;
-
- data.client = client;
- data.criteria = criteria;
-
- return db_walk(name, &find_visitor, &data, error_r);
-}
-
-static void printSearchStats(struct client *client, SearchStats *stats)
-{
- client_printf(client, "songs: %i\n", stats->numberOfSongs);
- client_printf(client, "playtime: %li\n", stats->playTime);
-}
-
-static bool
-stats_visitor_song(struct song *song, void *data,
- G_GNUC_UNUSED GError **error_r)
-{
- SearchStats *stats = data;
-
- if (locate_song_match(song, stats->criteria)) {
- stats->numberOfSongs++;
- stats->playTime += song_get_duration(song);
- }
-
- return true;
-}
-
-static const struct db_visitor stats_visitor = {
- .song = stats_visitor_song,
-};
-
-bool
-searchStatsForSongsIn(struct client *client, const char *name,
- const struct locate_item_list *criteria,
- GError **error_r)
-{
- SearchStats stats;
-
- stats.criteria = criteria;
- stats.numberOfSongs = 0;
- stats.playTime = 0;
-
- if (!db_walk(name, &stats_visitor, &stats, error_r))
- return false;
-
- printSearchStats(client, &stats);
- return true;
-}
-
-bool
-printAllIn(struct client *client, const char *uri_utf8, GError **error_r)
-{
- struct db_selection selection;
- db_selection_init(&selection, uri_utf8, true);
- return db_selection_print(client, &selection, false, error_r);
-}
-
-bool
-printInfoForAllIn(struct client *client, const char *uri_utf8,
- GError **error_r)
-{
- struct db_selection selection;
- db_selection_init(&selection, uri_utf8, true);
- return db_selection_print(client, &selection, true, error_r);
-}
-
-static ListCommandItem *
-newListCommandItem(int tagType, const struct locate_item_list *criteria)
-{
- ListCommandItem *item = g_new(ListCommandItem, 1);
-
- item->tagType = tagType;
- item->criteria = criteria;
-
- return item;
-}
-
-static void freeListCommandItem(ListCommandItem * item)
-{
- g_free(item);
-}
-
-static void
-visitTag(struct client *client, struct strset *set,
- struct song *song, enum tag_type tagType)
-{
- struct tag *tag = song->tag;
- bool found = false;
-
- if (tagType == LOCATE_TAG_FILE_TYPE) {
- song_print_uri(client, song);
- return;
- }
-
- if (!tag)
- return;
-
- for (unsigned i = 0; i < tag->num_items; i++) {
- if (tag->items[i]->type == tagType) {
- strset_add(set, tag->items[i]->value);
- found = true;
- }
- }
-
- if (!found)
- strset_add(set, "");
-}
-
-struct list_tags_data {
- struct client *client;
- ListCommandItem *item;
- struct strset *set;
-};
-
-static bool
-unique_tags_visitor_song(struct song *song, void *_data,
- G_GNUC_UNUSED GError **error_r)
-{
- struct list_tags_data *data = _data;
- ListCommandItem *item = data->item;
-
- if (locate_song_match(song, item->criteria))
- visitTag(data->client, data->set, song, item->tagType);
-
- return true;
-}
-
-static const struct db_visitor unique_tags_visitor = {
- .song = unique_tags_visitor_song,
-};
-
-bool
-listAllUniqueTags(struct client *client, int type,
- const struct locate_item_list *criteria,
- GError **error_r)
-{
- ListCommandItem *item = newListCommandItem(type, criteria);
- struct list_tags_data data = {
- .client = client,
- .item = item,
- };
-
- if (type >= 0 && type <= TAG_NUM_OF_ITEM_TYPES) {
- data.set = strset_new();
- }
-
- if (!db_walk("", &unique_tags_visitor, &data, error_r)) {
- freeListCommandItem(item);
- return false;
- }
-
- if (type >= 0 && type <= TAG_NUM_OF_ITEM_TYPES) {
- const char *value;
-
- strset_rewind(data.set);
-
- while ((value = strset_next(data.set)) != NULL)
- client_printf(client, "%s: %s\n",
- tag_item_names[type],
- value);
-
- strset_free(data.set);
- }
-
- freeListCommandItem(item);
-
- return true;
-}
diff --git a/src/db_print.h b/src/db_print.h
deleted file mode 100644
index 1b957da18..000000000
--- a/src/db_print.h
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * 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_DB_PRINT_H
-#define MPD_DB_PRINT_H
-
-#include "gcc.h"
-
-#include <glib.h>
-#include <stdbool.h>
-
-struct client;
-struct locate_item_list;
-struct db_selection;
-struct db_visitor;
-
-gcc_nonnull(1,2)
-bool
-db_selection_print(struct client *client, const struct db_selection *selection,
- bool full, GError **error_r);
-
-gcc_nonnull(1,2)
-bool
-printAllIn(struct client *client, const char *uri_utf8, GError **error_r);
-
-gcc_nonnull(1,2)
-bool
-printInfoForAllIn(struct client *client, const char *uri_utf8,
- GError **error_r);
-
-gcc_nonnull(1,2,3)
-bool
-searchForSongsIn(struct client *client, const char *name,
- const struct locate_item_list *criteria,
- GError **error_r);
-
-gcc_nonnull(1,2,3)
-bool
-findSongsIn(struct client *client, const char *name,
- const struct locate_item_list *criteria,
- GError **error_r);
-
-gcc_nonnull(1,2,3)
-bool
-searchStatsForSongsIn(struct client *client, const char *name,
- const struct locate_item_list *criteria,
- GError **error_r);
-
-gcc_nonnull(1,3)
-bool
-listAllUniqueTags(struct client *client, int type,
- const struct locate_item_list *criteria,
- GError **error_r);
-
-#endif
diff --git a/src/db_save.c b/src/db_save.c
deleted file mode 100644
index 4af9d58b8..000000000
--- a/src/db_save.c
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "db_save.h"
-#include "db_lock.h"
-#include "directory.h"
-#include "directory_save.h"
-#include "song.h"
-#include "path.h"
-#include "text_file.h"
-#include "tag.h"
-#include "tag_internal.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <stdlib.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "database"
-
-#define DIRECTORY_INFO_BEGIN "info_begin"
-#define DIRECTORY_INFO_END "info_end"
-#define DB_FORMAT_PREFIX "format: "
-#define DIRECTORY_MPD_VERSION "mpd_version: "
-#define DIRECTORY_FS_CHARSET "fs_charset: "
-#define DB_TAG_PREFIX "tag: "
-
-enum {
- DB_FORMAT = 1,
-};
-
-G_GNUC_CONST
-static GQuark
-db_quark(void)
-{
- return g_quark_from_static_string("database");
-}
-
-void
-db_save_internal(FILE *fp, const struct directory *music_root)
-{
- assert(music_root != NULL);
-
- fprintf(fp, "%s\n", DIRECTORY_INFO_BEGIN);
- fprintf(fp, DB_FORMAT_PREFIX "%u\n", DB_FORMAT);
- fprintf(fp, "%s%s\n", DIRECTORY_MPD_VERSION, VERSION);
- fprintf(fp, "%s%s\n", DIRECTORY_FS_CHARSET, path_get_fs_charset());
-
- for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
- if (!ignore_tag_items[i])
- fprintf(fp, DB_TAG_PREFIX "%s\n", tag_item_names[i]);
-
- fprintf(fp, "%s\n", DIRECTORY_INFO_END);
-
- directory_save(fp, music_root);
-}
-
-bool
-db_load_internal(FILE *fp, struct directory *music_root, GError **error)
-{
- GString *buffer = g_string_sized_new(1024);
- char *line;
- int format = 0;
- bool found_charset = false, found_version = false;
- bool success;
- bool tags[TAG_NUM_OF_ITEM_TYPES];
-
- assert(music_root != NULL);
-
- /* get initial info */
- line = read_text_line(fp, buffer);
- if (line == NULL || strcmp(DIRECTORY_INFO_BEGIN, line) != 0) {
- g_set_error(error, db_quark(), 0, "Database corrupted");
- g_string_free(buffer, true);
- return false;
- }
-
- memset(tags, false, sizeof(tags));
-
- while ((line = read_text_line(fp, buffer)) != NULL &&
- strcmp(line, DIRECTORY_INFO_END) != 0) {
- if (g_str_has_prefix(line, DB_FORMAT_PREFIX)) {
- format = atoi(line + sizeof(DB_FORMAT_PREFIX) - 1);
- } else if (g_str_has_prefix(line, DIRECTORY_MPD_VERSION)) {
- if (found_version) {
- g_set_error(error, db_quark(), 0,
- "Duplicate version line");
- g_string_free(buffer, true);
- return false;
- }
-
- found_version = true;
- } else if (g_str_has_prefix(line, DIRECTORY_FS_CHARSET)) {
- const char *new_charset, *old_charset;
-
- if (found_charset) {
- g_set_error(error, db_quark(), 0,
- "Duplicate charset line");
- g_string_free(buffer, true);
- return false;
- }
-
- found_charset = true;
-
- new_charset = line + sizeof(DIRECTORY_FS_CHARSET) - 1;
- old_charset = path_get_fs_charset();
- if (old_charset != NULL
- && strcmp(new_charset, old_charset)) {
- g_set_error(error, db_quark(), 0,
- "Existing database has charset "
- "\"%s\" instead of \"%s\"; "
- "discarding database file",
- new_charset, old_charset);
- g_string_free(buffer, true);
- return false;
- }
- } else if (g_str_has_prefix(line, DB_TAG_PREFIX)) {
- const char *name = line + sizeof(DB_TAG_PREFIX) - 1;
- enum tag_type tag = tag_name_parse(name);
- if (tag == TAG_NUM_OF_ITEM_TYPES) {
- g_set_error(error, db_quark(), 0,
- "Unrecognized tag '%s', "
- "discarding database file",
- name);
- return false;
- }
-
- tags[tag] = true;
- } else {
- g_set_error(error, db_quark(), 0,
- "Malformed line: %s", line);
- g_string_free(buffer, true);
- return false;
- }
- }
-
- if (format != DB_FORMAT) {
- g_set_error(error, db_quark(), 0,
- "Database format mismatch, "
- "discarding database file");
- return false;
- }
-
- for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) {
- if (!ignore_tag_items[i] && !tags[i]) {
- g_set_error(error, db_quark(), 0,
- "Tag list mismatch, "
- "discarding database file");
- return false;
- }
- }
-
- g_debug("reading DB");
-
- db_lock();
- success = directory_load(fp, music_root, buffer, error);
- db_unlock();
- g_string_free(buffer, true);
-
- return success;
-}
diff --git a/src/db_save.h b/src/db_save.h
deleted file mode 100644
index e760ec881..000000000
--- a/src/db_save.h
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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_DB_SAVE_H
-#define MPD_DB_SAVE_H
-
-#include <glib.h>
-#include <stdbool.h>
-#include <stdio.h>
-
-struct directory;
-
-void
-db_save_internal(FILE *file, const struct directory *root);
-
-bool
-db_load_internal(FILE *file, struct directory *root, GError **error);
-
-#endif
diff --git a/src/db_selection.h b/src/db_selection.h
deleted file mode 100644
index 2cebb4907..000000000
--- a/src/db_selection.h
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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_DB_SELECTION_H
-#define MPD_DB_SELECTION_H
-
-#include "gcc.h"
-
-#include <assert.h>
-
-struct directory;
-struct song;
-
-struct db_selection {
- /**
- * The base URI of the search (UTF-8). Must not begin or end
- * with a slash. NULL or an empty string searches the whole
- * database.
- */
- const char *uri;
-
- /**
- * Recursively search all sub directories?
- */
- bool recursive;
-};
-
-gcc_nonnull(1,2)
-static inline void
-db_selection_init(struct db_selection *selection,
- const char *uri, bool recursive)
-{
- assert(selection != NULL);
- assert(uri != NULL);
-
- selection->uri = uri;
- selection->recursive = recursive;
-}
-
-#endif
diff --git a/src/db_visitor.h b/src/db_visitor.h
deleted file mode 100644
index 6b90c1868..000000000
--- a/src/db_visitor.h
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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_DB_VISITOR_H
-#define MPD_DB_VISITOR_H
-
-struct directory;
-struct song;
-struct playlist_metadata;
-
-struct db_visitor {
- /**
- * Visit a directory. Optional method.
- *
- * @return true to continue the operation, false on error (set error_r)
- */
- bool (*directory)(const struct directory *directory, void *ctx,
- GError **error_r);
-
- /**
- * Visit a song. Optional method.
- *
- * @return true to continue the operation, false on error (set error_r)
- */
- bool (*song)(struct song *song, void *ctx, GError **error_r);
-
- /**
- * Visit a playlist. Optional method.
- *
- * @param directory the directory the playlist resides in
- * @return true to continue the operation, false on error (set error_r)
- */
- bool (*playlist)(const struct playlist_metadata *playlist,
- const struct directory *directory, void *ctx,
- GError **error_r);
-};
-
-#endif
diff --git a/src/decoder/AdPlugDecoderPlugin.cxx b/src/decoder/AdPlugDecoderPlugin.cxx
new file mode 100644
index 000000000..47ab1a7f3
--- /dev/null
+++ b/src/decoder/AdPlugDecoderPlugin.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 "AdPlugDecoderPlugin.h"
+#include "TagHandler.hxx"
+#include "DecoderAPI.hxx"
+#include "CheckAudioFormat.hxx"
+
+#include <adplug/adplug.h>
+#include <adplug/emuopl.h>
+
+#include <glib.h>
+
+#include <assert.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "adplug"
+
+static unsigned sample_rate;
+
+static bool
+adplug_init(const config_param &param)
+{
+ GError *error = NULL;
+
+ sample_rate = param.GetBlockValue("sample_rate", 48000u);
+ if (!audio_check_sample_rate(sample_rate, &error)) {
+ g_warning("%s\n", error->message);
+ g_error_free(error);
+ return false;
+ }
+
+ return true;
+}
+
+static void
+adplug_file_decode(struct decoder *decoder, const char *path_fs)
+{
+ CEmuopl opl(sample_rate, true, true);
+ opl.init();
+
+ CPlayer *player = CAdPlug::factory(path_fs, &opl);
+ if (player == nullptr)
+ return;
+
+ const AudioFormat audio_format(sample_rate, SampleFormat::S16, 2);
+ assert(audio_format.IsValid());
+
+ decoder_initialized(decoder, audio_format, false,
+ player->songlength() / 1000.);
+
+ int16_t buffer[2048];
+ const unsigned frames_per_buffer = G_N_ELEMENTS(buffer) / 2;
+ enum decoder_command cmd;
+
+ do {
+ if (!player->update())
+ break;
+
+ opl.update(buffer, frames_per_buffer);
+ cmd = decoder_data(decoder, NULL,
+ buffer, sizeof(buffer),
+ 0);
+ } while (cmd == DECODE_COMMAND_NONE);
+
+ delete player;
+}
+
+static void
+adplug_scan_tag(enum tag_type type, const std::string &value,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ if (!value.empty())
+ tag_handler_invoke_tag(handler, handler_ctx,
+ type, value.c_str());
+}
+
+static bool
+adplug_scan_file(const char *path_fs,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ CEmuopl opl(sample_rate, true, true);
+ opl.init();
+
+ CPlayer *player = CAdPlug::factory(path_fs, &opl);
+ if (player == nullptr)
+ return false;
+
+ tag_handler_invoke_duration(handler, handler_ctx,
+ player->songlength() / 1000);
+
+ if (handler->tag != nullptr) {
+ adplug_scan_tag(TAG_TITLE, player->gettitle(),
+ handler, handler_ctx);
+ adplug_scan_tag(TAG_ARTIST, player->getauthor(),
+ handler, handler_ctx);
+ adplug_scan_tag(TAG_COMMENT, player->getdesc(),
+ handler, handler_ctx);
+ }
+
+ delete player;
+ return true;
+}
+
+static const char *const adplug_suffixes[] = {
+ "amd",
+ "d00",
+ "hsc",
+ "laa",
+ "rad",
+ "raw",
+ "sa2",
+ nullptr
+};
+
+const struct decoder_plugin adplug_decoder_plugin = {
+ "adplug",
+ adplug_init,
+ nullptr,
+ nullptr,
+ adplug_file_decode,
+ adplug_scan_file,
+ nullptr,
+ nullptr,
+ adplug_suffixes,
+ nullptr,
+};
diff --git a/src/decoder/AdPlugDecoderPlugin.h b/src/decoder/AdPlugDecoderPlugin.h
new file mode 100644
index 000000000..9fdf438aa
--- /dev/null
+++ b/src/decoder/AdPlugDecoderPlugin.h
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+#ifndef MPD_DECODER_ADPLUG_H
+#define MPD_DECODER_ADPLUG_H
+
+extern const struct decoder_plugin adplug_decoder_plugin;
+
+#endif
diff --git a/src/decoder/AudiofileDecoderPlugin.cxx b/src/decoder/AudiofileDecoderPlugin.cxx
new file mode 100644
index 000000000..9c00b20ce
--- /dev/null
+++ b/src/decoder/AudiofileDecoderPlugin.cxx
@@ -0,0 +1,264 @@
+/*
+ * 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 "AudiofileDecoderPlugin.hxx"
+#include "DecoderAPI.hxx"
+#include "CheckAudioFormat.hxx"
+#include "TagHandler.hxx"
+
+#include <audiofile.h>
+#include <af_vfs.h>
+#include <assert.h>
+#include <glib.h>
+#include <stdio.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "audiofile"
+
+/* pick 1020 since its devisible for 8,16,24, and 32-bit audio */
+#define CHUNK_SIZE 1020
+
+static int audiofile_get_duration(const char *file)
+{
+ int total_time;
+ AFfilehandle af_fp = afOpenFile(file, "r", nullptr);
+ if (af_fp == AF_NULL_FILEHANDLE) {
+ return -1;
+ }
+ total_time = (int)
+ ((double)afGetFrameCount(af_fp, AF_DEFAULT_TRACK)
+ / afGetRate(af_fp, AF_DEFAULT_TRACK));
+ afCloseFile(af_fp);
+ return total_time;
+}
+
+static ssize_t
+audiofile_file_read(AFvirtualfile *vfile, void *data, size_t length)
+{
+ struct input_stream *is = (struct input_stream *) vfile->closure;
+ GError *error = nullptr;
+ size_t nbytes;
+
+ nbytes = input_stream_lock_read(is, data, length, &error);
+ if (nbytes == 0 && error != nullptr) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return -1;
+ }
+
+ return nbytes;
+}
+
+static AFfileoffset
+audiofile_file_length(AFvirtualfile *vfile)
+{
+ struct input_stream *is = (struct input_stream *) vfile->closure;
+ return input_stream_get_size(is);
+}
+
+static AFfileoffset
+audiofile_file_tell(AFvirtualfile *vfile)
+{
+ struct input_stream *is = (struct input_stream *) vfile->closure;
+ return input_stream_get_offset(is);
+}
+
+static void
+audiofile_file_destroy(AFvirtualfile *vfile)
+{
+ assert(vfile->closure != nullptr);
+
+ vfile->closure = nullptr;
+}
+
+static AFfileoffset
+audiofile_file_seek(AFvirtualfile *vfile, AFfileoffset offset, int is_relative)
+{
+ struct input_stream *is = (struct input_stream *) vfile->closure;
+ int whence = (is_relative ? SEEK_CUR : SEEK_SET);
+ if (input_stream_lock_seek(is, offset, whence, nullptr)) {
+ return input_stream_get_offset(is);
+ } else {
+ return -1;
+ }
+}
+
+static AFvirtualfile *
+setup_virtual_fops(struct input_stream *stream)
+{
+ AFvirtualfile *vf = new AFvirtualfile();
+ vf->closure = stream;
+ vf->write = nullptr;
+ vf->read = audiofile_file_read;
+ vf->length = audiofile_file_length;
+ vf->destroy = audiofile_file_destroy;
+ vf->seek = audiofile_file_seek;
+ vf->tell = audiofile_file_tell;
+ return vf;
+}
+
+static SampleFormat
+audiofile_bits_to_sample_format(int bits)
+{
+ switch (bits) {
+ case 8:
+ return SampleFormat::S8;
+
+ case 16:
+ return SampleFormat::S16;
+
+ case 24:
+ return SampleFormat::S24_P32;
+
+ case 32:
+ return SampleFormat::S32;
+ }
+
+ return SampleFormat::UNDEFINED;
+}
+
+static SampleFormat
+audiofile_setup_sample_format(AFfilehandle af_fp)
+{
+ int fs, bits;
+
+ afGetSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits);
+ if (!audio_valid_sample_format(audiofile_bits_to_sample_format(bits))) {
+ g_debug("input file has %d bit samples, converting to 16",
+ bits);
+ bits = 16;
+ }
+
+ afSetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK,
+ AF_SAMPFMT_TWOSCOMP, bits);
+ afGetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits);
+
+ return audiofile_bits_to_sample_format(bits);
+}
+
+static void
+audiofile_stream_decode(struct decoder *decoder, struct input_stream *is)
+{
+ GError *error = nullptr;
+ AFvirtualfile *vf;
+ int fs, frame_count;
+ AFfilehandle af_fp;
+ AudioFormat audio_format;
+ float total_time;
+ uint16_t bit_rate;
+ int ret;
+ char chunk[CHUNK_SIZE];
+ enum decoder_command cmd;
+
+ if (!input_stream_is_seekable(is)) {
+ g_warning("not seekable");
+ return;
+ }
+
+ vf = setup_virtual_fops(is);
+
+ af_fp = afOpenVirtualFile(vf, "r", nullptr);
+ if (af_fp == AF_NULL_FILEHANDLE) {
+ g_warning("failed to input stream\n");
+ return;
+ }
+
+ if (!audio_format_init_checked(audio_format,
+ afGetRate(af_fp, AF_DEFAULT_TRACK),
+ audiofile_setup_sample_format(af_fp),
+ afGetVirtualChannels(af_fp, AF_DEFAULT_TRACK),
+ &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ afCloseFile(af_fp);
+ return;
+ }
+
+ frame_count = afGetFrameCount(af_fp, AF_DEFAULT_TRACK);
+
+ total_time = ((float)frame_count / (float)audio_format.sample_rate);
+
+ bit_rate = (uint16_t)(input_stream_get_size(is) * 8.0 / total_time / 1000.0 + 0.5);
+
+ fs = (int)afGetVirtualFrameSize(af_fp, AF_DEFAULT_TRACK, 1);
+
+ decoder_initialized(decoder, audio_format, true, total_time);
+
+ do {
+ ret = afReadFrames(af_fp, AF_DEFAULT_TRACK, chunk,
+ CHUNK_SIZE / fs);
+ if (ret <= 0)
+ break;
+
+ cmd = decoder_data(decoder, nullptr,
+ chunk, ret * fs,
+ bit_rate);
+
+ if (cmd == DECODE_COMMAND_SEEK) {
+ AFframecount frame = decoder_seek_where(decoder) *
+ audio_format.sample_rate;
+ afSeekFrame(af_fp, AF_DEFAULT_TRACK, frame);
+
+ decoder_command_finished(decoder);
+ cmd = DECODE_COMMAND_NONE;
+ }
+ } while (cmd == DECODE_COMMAND_NONE);
+
+ afCloseFile(af_fp);
+}
+
+static bool
+audiofile_scan_file(const char *file,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ int total_time = audiofile_get_duration(file);
+
+ if (total_time < 0) {
+ g_debug("Failed to get total song time from: %s\n",
+ file);
+ return false;
+ }
+
+ tag_handler_invoke_duration(handler, handler_ctx, total_time);
+ return true;
+}
+
+static const char *const audiofile_suffixes[] = {
+ "wav", "au", "aiff", "aif", nullptr
+};
+
+static const char *const audiofile_mime_types[] = {
+ "audio/x-wav",
+ "audio/x-aiff",
+ nullptr
+};
+
+const struct decoder_plugin audiofile_decoder_plugin = {
+ "audiofile",
+ nullptr,
+ nullptr,
+ audiofile_stream_decode,
+ nullptr,
+ audiofile_scan_file,
+ nullptr,
+ nullptr,
+ audiofile_suffixes,
+ audiofile_mime_types,
+};
diff --git a/src/decoder/AudiofileDecoderPlugin.hxx b/src/decoder/AudiofileDecoderPlugin.hxx
new file mode 100644
index 000000000..59c09c006
--- /dev/null
+++ b/src/decoder/AudiofileDecoderPlugin.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_DECODER_AUDIOFILE_HXX
+#define MPD_DECODER_AUDIOFILE_HXX
+
+extern const struct decoder_plugin audiofile_decoder_plugin;
+
+#endif
diff --git a/src/decoder/DsdLib.cxx b/src/decoder/DsdLib.cxx
new file mode 100644
index 000000000..d18131184
--- /dev/null
+++ b/src/decoder/DsdLib.cxx
@@ -0,0 +1,169 @@
+/*
+ * 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
+ *
+ * This file contains functions used by the DSF and DSDIFF decoders.
+ *
+ */
+
+#include "config.h"
+#include "DsdLib.hxx"
+#include "DecoderAPI.hxx"
+#include "util/bit_reverse.h"
+#include "TagHandler.hxx"
+#include "TagId3.hxx"
+
+#include <unistd.h>
+#include <string.h>
+#include <stdio.h> /* for SEEK_SET, SEEK_CUR */
+
+#ifdef HAVE_ID3TAG
+#include <id3tag.h>
+#endif
+
+bool
+dsdlib_id_equals(const struct dsdlib_id *id, const char *s)
+{
+ assert(id != nullptr);
+ assert(s != nullptr);
+ assert(strlen(s) == sizeof(id->value));
+
+ return memcmp(id->value, s, sizeof(id->value)) == 0;
+}
+
+bool
+dsdlib_read(struct decoder *decoder, struct input_stream *is,
+ void *data, size_t length)
+{
+ size_t nbytes = decoder_read(decoder, is, data, length);
+ return nbytes == length;
+}
+
+/**
+ * Skip the #input_stream to the specified offset.
+ */
+bool
+dsdlib_skip_to(struct decoder *decoder, struct input_stream *is,
+ goffset offset)
+{
+ if (input_stream_is_seekable(is))
+ return input_stream_seek(is, offset, SEEK_SET, nullptr);
+
+ if (input_stream_get_offset(is) > offset)
+ return false;
+
+ char buffer[8192];
+ while (input_stream_get_offset(is) < offset) {
+ size_t length = sizeof(buffer);
+ if (offset - input_stream_get_offset(is) < (goffset)length)
+ length = offset - input_stream_get_offset(is);
+
+ size_t nbytes = decoder_read(decoder, is, buffer, length);
+ if (nbytes == 0)
+ return false;
+ }
+
+ assert(input_stream_get_offset(is) == offset);
+ return true;
+}
+
+/**
+ * Skip some bytes from the #input_stream.
+ */
+bool
+dsdlib_skip(struct decoder *decoder, struct input_stream *is,
+ goffset delta)
+{
+ assert(delta >= 0);
+
+ if (delta == 0)
+ return true;
+
+ if (input_stream_is_seekable(is))
+ return input_stream_seek(is, delta, SEEK_CUR, nullptr);
+
+ char buffer[8192];
+ while (delta > 0) {
+ size_t length = sizeof(buffer);
+ if ((goffset)length > delta)
+ length = delta;
+
+ size_t nbytes = decoder_read(decoder, is, buffer, length);
+ if (nbytes == 0)
+ return false;
+
+ delta -= nbytes;
+ }
+
+ return true;
+}
+
+/**
+ * Add tags from ID3 tag. All tags commonly found in the ID3 tags of
+ * DSF and DSDIFF files are imported
+ */
+
+#ifdef HAVE_ID3TAG
+void
+dsdlib_tag_id3(struct input_stream *is,
+ const struct tag_handler *handler,
+ void *handler_ctx, goffset tagoffset)
+{
+ assert(tagoffset >= 0);
+
+ if (tagoffset == 0)
+ return;
+
+ if (!dsdlib_skip_to(nullptr, is, tagoffset))
+ return;
+
+ struct id3_tag *id3_tag = nullptr;
+ id3_length_t count;
+
+ /* Prevent broken files causing problems */
+ const goffset size = input_stream_get_size(is);
+ const goffset offset = input_stream_get_offset(is);
+ if (offset >= size)
+ return;
+
+ count = size - offset;
+
+ /* Check and limit id3 tag size to prevent a stack overflow */
+ if (count == 0 || count > 4096)
+ return;
+
+ id3_byte_t dsdid3[count];
+ id3_byte_t *dsdid3data;
+ dsdid3data = dsdid3;
+
+ if (!dsdlib_read(nullptr, is, dsdid3data, count))
+ return;
+
+ id3_tag = id3_tag_parse(dsdid3data, count);
+ if (id3_tag == nullptr)
+ return;
+
+ scan_id3_tag(id3_tag, handler, handler_ctx);
+
+ id3_tag_delete(id3_tag);
+
+ return;
+}
+#endif
diff --git a/src/decoder/DsdLib.hxx b/src/decoder/DsdLib.hxx
new file mode 100644
index 000000000..2a8e15190
--- /dev/null
+++ b/src/decoder/DsdLib.hxx
@@ -0,0 +1,51 @@
+/*
+ * 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_DECODER_DSDLIB_HXX
+#define MPD_DECODER_DSDLIB_HXX
+
+#include <stdlib.h>
+
+#include <glib.h>
+
+struct dsdlib_id {
+ char value[4];
+};
+
+bool
+dsdlib_id_equals(const struct dsdlib_id *id, const char *s);
+
+bool
+dsdlib_read(struct decoder *decoder, struct input_stream *is,
+ void *data, size_t length);
+
+bool
+dsdlib_skip_to(struct decoder *decoder, struct input_stream *is,
+ goffset offset);
+
+bool
+dsdlib_skip(struct decoder *decoder, struct input_stream *is,
+ goffset delta);
+
+void
+dsdlib_tag_id3(struct input_stream *is,
+ const struct tag_handler *handler,
+ void *handler_ctx, goffset tagoffset);
+
+#endif
diff --git a/src/decoder/DsdiffDecoderPlugin.cxx b/src/decoder/DsdiffDecoderPlugin.cxx
new file mode 100644
index 000000000..9e9dab4d2
--- /dev/null
+++ b/src/decoder/DsdiffDecoderPlugin.cxx
@@ -0,0 +1,530 @@
+/*
+ * 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
+ *
+ * This plugin decodes DSDIFF data (SACD) embedded in DFF files.
+ * The DFF code was modeled after the specification found here:
+ * http://www.sonicstudio.com/pdf/dsd/DSDIFF_1.5_Spec.pdf
+ *
+ * All functions common to both DSD decoders have been moved to dsdlib
+ */
+
+#include "config.h"
+#include "DsdiffDecoderPlugin.hxx"
+#include "DecoderAPI.hxx"
+#include "CheckAudioFormat.hxx"
+#include "util/bit_reverse.h"
+#include "TagHandler.hxx"
+#include "DsdLib.hxx"
+#include "TagHandler.hxx"
+
+#include <unistd.h>
+#include <stdio.h> /* for SEEK_SET, SEEK_CUR */
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "dsdiff"
+
+struct DsdiffHeader {
+ struct dsdlib_id id;
+ uint32_t size_high, size_low;
+ struct dsdlib_id format;
+};
+
+struct DsdiffChunkHeader {
+ struct dsdlib_id id;
+ uint32_t size_high, size_low;
+
+ /**
+ * Read the "size" attribute from the specified header, converting it
+ * to the host byte order if needed.
+ */
+ gcc_const
+ uint64_t GetSize() const {
+ return (((uint64_t)GUINT32_FROM_BE(size_high)) << 32) |
+ ((uint64_t)GUINT32_FROM_BE(size_low));
+ }
+};
+
+/** struct for DSDIFF native Artist and Title tags */
+struct dsdiff_native_tag {
+ uint32_t size;
+};
+
+struct DsdiffMetaData {
+ unsigned sample_rate, channels;
+ bool bitreverse;
+ uint64_t chunk_size;
+#ifdef HAVE_ID3TAG
+ goffset id3_offset;
+ uint64_t id3_size;
+#endif
+ /** offset for artist tag */
+ goffset diar_offset;
+ /** offset for title tag */
+ goffset diti_offset;
+};
+
+static bool lsbitfirst;
+
+static bool
+dsdiff_init(const config_param &param)
+{
+ lsbitfirst = param.GetBlockValue("lsbitfirst", false);
+ return true;
+}
+
+static bool
+dsdiff_read_id(struct decoder *decoder, struct input_stream *is,
+ struct dsdlib_id *id)
+{
+ return dsdlib_read(decoder, is, id, sizeof(*id));
+}
+
+static bool
+dsdiff_read_chunk_header(struct decoder *decoder, struct input_stream *is,
+ DsdiffChunkHeader *header)
+{
+ return dsdlib_read(decoder, is, header, sizeof(*header));
+}
+
+static bool
+dsdiff_read_payload(struct decoder *decoder, struct input_stream *is,
+ const DsdiffChunkHeader *header,
+ void *data, size_t length)
+{
+ uint64_t size = header->GetSize();
+ if (size != (uint64_t)length)
+ return false;
+
+ size_t nbytes = decoder_read(decoder, is, data, length);
+ return nbytes == length;
+}
+
+/**
+ * Read and parse a "SND" chunk inside "PROP".
+ */
+static bool
+dsdiff_read_prop_snd(struct decoder *decoder, struct input_stream *is,
+ DsdiffMetaData *metadata,
+ goffset end_offset)
+{
+ DsdiffChunkHeader header;
+ while ((goffset)(input_stream_get_offset(is) + sizeof(header)) <= end_offset) {
+ if (!dsdiff_read_chunk_header(decoder, is, &header))
+ return false;
+
+ goffset chunk_end_offset = input_stream_get_offset(is)
+ + header.GetSize();
+ if (chunk_end_offset > end_offset)
+ return false;
+
+ if (dsdlib_id_equals(&header.id, "FS ")) {
+ uint32_t sample_rate;
+ if (!dsdiff_read_payload(decoder, is, &header,
+ &sample_rate,
+ sizeof(sample_rate)))
+ return false;
+
+ metadata->sample_rate = GUINT32_FROM_BE(sample_rate);
+ } else if (dsdlib_id_equals(&header.id, "CHNL")) {
+ uint16_t channels;
+ if (header.GetSize() < sizeof(channels) ||
+ !dsdlib_read(decoder, is,
+ &channels, sizeof(channels)) ||
+ !dsdlib_skip_to(decoder, is, chunk_end_offset))
+ return false;
+
+ metadata->channels = GUINT16_FROM_BE(channels);
+ } else if (dsdlib_id_equals(&header.id, "CMPR")) {
+ struct dsdlib_id type;
+ if (header.GetSize() < sizeof(type) ||
+ !dsdlib_read(decoder, is,
+ &type, sizeof(type)) ||
+ !dsdlib_skip_to(decoder, is, chunk_end_offset))
+ return false;
+
+ if (!dsdlib_id_equals(&type, "DSD "))
+ /* only uncompressed DSD audio data
+ is implemented */
+ return false;
+ } else {
+ /* ignore unknown chunk */
+
+ if (!dsdlib_skip_to(decoder, is, chunk_end_offset))
+ return false;
+ }
+ }
+
+ return input_stream_get_offset(is) == end_offset;
+}
+
+/**
+ * Read and parse a "PROP" chunk.
+ */
+static bool
+dsdiff_read_prop(struct decoder *decoder, struct input_stream *is,
+ DsdiffMetaData *metadata,
+ const DsdiffChunkHeader *prop_header)
+{
+ uint64_t prop_size = prop_header->GetSize();
+ goffset end_offset = input_stream_get_offset(is) + prop_size;
+
+ struct dsdlib_id prop_id;
+ if (prop_size < sizeof(prop_id) ||
+ !dsdiff_read_id(decoder, is, &prop_id))
+ return false;
+
+ if (dsdlib_id_equals(&prop_id, "SND "))
+ return dsdiff_read_prop_snd(decoder, is, metadata, end_offset);
+ else
+ /* ignore unknown PROP chunk */
+ return dsdlib_skip_to(decoder, is, end_offset);
+}
+
+static void
+dsdiff_handle_native_tag(struct input_stream *is,
+ const struct tag_handler *handler,
+ void *handler_ctx, goffset tagoffset,
+ enum tag_type type)
+{
+ if (!dsdlib_skip_to(nullptr, is, tagoffset))
+ return;
+
+ struct dsdiff_native_tag metatag;
+
+ if (!dsdlib_read(nullptr, is, &metatag, sizeof(metatag)))
+ return;
+
+ uint32_t length = GUINT32_FROM_BE(metatag.size);
+
+ /* Check and limit size of the tag to prevent a stack overflow */
+ if (length == 0 || length > 60)
+ return;
+
+ char string[length];
+ char *label;
+ label = string;
+
+ if (!dsdlib_read(nullptr, is, label, (size_t)length))
+ return;
+
+ string[length] = '\0';
+ tag_handler_invoke_tag(handler, handler_ctx, type, label);
+ return;
+}
+
+/**
+ * Read and parse additional metadata chunks for tagging purposes. By default
+ * dsdiff files only support equivalents for artist and title but some of the
+ * extract tools add an id3 tag to provide more tags. If such id3 is found
+ * this will be used for tagging otherwise the native tags (if any) will be
+ * used
+ */
+
+static bool
+dsdiff_read_metadata_extra(struct decoder *decoder, struct input_stream *is,
+ DsdiffMetaData *metadata,
+ DsdiffChunkHeader *chunk_header,
+ const struct tag_handler *handler,
+ void *handler_ctx)
+{
+
+ /* skip from DSD data to next chunk header */
+ if (!dsdlib_skip(decoder, is, metadata->chunk_size))
+ return false;
+ if (!dsdiff_read_chunk_header(decoder, is, chunk_header))
+ return false;
+
+#ifdef HAVE_ID3TAG
+ metadata->id3_size = 0;
+#endif
+
+ /* Now process all the remaining chunk headers in the stream
+ and record their position and size */
+
+ const goffset size = input_stream_get_size(is);
+ while (input_stream_get_offset(is) < size) {
+ uint64_t chunk_size = chunk_header->GetSize();
+
+ /* DIIN chunk, is directly followed by other chunks */
+ if (dsdlib_id_equals(&chunk_header->id, "DIIN"))
+ chunk_size = 0;
+
+ /* DIAR chunk - DSDIFF native tag for Artist */
+ if (dsdlib_id_equals(&chunk_header->id, "DIAR")) {
+ chunk_size = chunk_header->GetSize();
+ metadata->diar_offset = input_stream_get_offset(is);
+ }
+
+ /* DITI chunk - DSDIFF native tag for Title */
+ if (dsdlib_id_equals(&chunk_header->id, "DITI")) {
+ chunk_size = chunk_header->GetSize();
+ metadata->diti_offset = input_stream_get_offset(is);
+ }
+#ifdef HAVE_ID3TAG
+ /* 'ID3 ' chunk, offspec. Used by sacdextract */
+ if (dsdlib_id_equals(&chunk_header->id, "ID3 ")) {
+ chunk_size = chunk_header->GetSize();
+ metadata->id3_offset = input_stream_get_offset(is);
+ metadata->id3_size = chunk_size;
+ }
+#endif
+ if (chunk_size != 0) {
+ if (!dsdlib_skip(decoder, is, chunk_size))
+ break;
+ }
+
+ if (input_stream_get_offset(is) < size) {
+ if (!dsdiff_read_chunk_header(decoder, is, chunk_header))
+ return false;
+ }
+ chunk_size = 0;
+ }
+ /* done processing chunk headers, process tags if any */
+
+#ifdef HAVE_ID3TAG
+ if (metadata->id3_offset != 0)
+ {
+ /* a ID3 tag has preference over the other tags, do not process
+ other tags if we have one */
+ dsdlib_tag_id3(is, handler, handler_ctx, metadata->id3_offset);
+ return true;
+ }
+#endif
+
+ if (metadata->diar_offset != 0)
+ dsdiff_handle_native_tag(is, handler, handler_ctx,
+ metadata->diar_offset, TAG_ARTIST);
+
+ if (metadata->diti_offset != 0)
+ dsdiff_handle_native_tag(is, handler, handler_ctx,
+ metadata->diti_offset, TAG_TITLE);
+ return true;
+}
+
+/**
+ * Read and parse all metadata chunks at the beginning. Stop when the
+ * first "DSD" chunk is seen, and return its header in the
+ * "chunk_header" parameter.
+ */
+static bool
+dsdiff_read_metadata(struct decoder *decoder, struct input_stream *is,
+ DsdiffMetaData *metadata,
+ DsdiffChunkHeader *chunk_header)
+{
+ DsdiffHeader header;
+ if (!dsdlib_read(decoder, is, &header, sizeof(header)) ||
+ !dsdlib_id_equals(&header.id, "FRM8") ||
+ !dsdlib_id_equals(&header.format, "DSD "))
+ return false;
+
+ while (true) {
+ if (!dsdiff_read_chunk_header(decoder, is,
+ chunk_header))
+ return false;
+
+ if (dsdlib_id_equals(&chunk_header->id, "PROP")) {
+ if (!dsdiff_read_prop(decoder, is, metadata,
+ chunk_header))
+ return false;
+ } else if (dsdlib_id_equals(&chunk_header->id, "DSD ")) {
+ const uint64_t chunk_size = chunk_header->GetSize();
+ metadata->chunk_size = chunk_size;
+ return true;
+ } else {
+ /* ignore unknown chunk */
+ const uint64_t chunk_size = chunk_header->GetSize();
+ goffset chunk_end_offset = input_stream_get_offset(is)
+ + chunk_size;
+
+ if (!dsdlib_skip_to(decoder, is, chunk_end_offset))
+ return false;
+ }
+ }
+}
+
+static void
+bit_reverse_buffer(uint8_t *p, uint8_t *end)
+{
+ for (; p < end; ++p)
+ *p = bit_reverse(*p);
+}
+
+/**
+ * Decode one "DSD" chunk.
+ */
+static bool
+dsdiff_decode_chunk(struct decoder *decoder, struct input_stream *is,
+ unsigned channels,
+ uint64_t chunk_size)
+{
+ uint8_t buffer[8192];
+
+ const size_t sample_size = sizeof(buffer[0]);
+ const size_t frame_size = channels * sample_size;
+ const unsigned buffer_frames = sizeof(buffer) / frame_size;
+ const unsigned buffer_samples = buffer_frames * frame_size;
+ const size_t buffer_size = buffer_samples * sample_size;
+
+ while (chunk_size > 0) {
+ /* see how much aligned data from the remaining chunk
+ fits into the local buffer */
+ unsigned now_frames = buffer_frames;
+ size_t now_size = buffer_size;
+ if (chunk_size < (uint64_t)now_size) {
+ now_frames = (unsigned)chunk_size / frame_size;
+ now_size = now_frames * frame_size;
+ }
+
+ size_t nbytes = decoder_read(decoder, is, buffer, now_size);
+ if (nbytes != now_size)
+ return false;
+
+ chunk_size -= nbytes;
+
+ if (lsbitfirst)
+ bit_reverse_buffer(buffer, buffer + nbytes);
+
+ enum decoder_command cmd =
+ decoder_data(decoder, is, buffer, nbytes, 0);
+ switch (cmd) {
+ case DECODE_COMMAND_NONE:
+ break;
+
+ case DECODE_COMMAND_START:
+ case DECODE_COMMAND_STOP:
+ return false;
+
+ case DECODE_COMMAND_SEEK:
+
+ /* Not implemented yet */
+ decoder_seek_error(decoder);
+ break;
+ }
+ }
+ return dsdlib_skip(decoder, is, chunk_size);
+}
+
+static void
+dsdiff_stream_decode(struct decoder *decoder, struct input_stream *is)
+{
+ DsdiffMetaData metadata;
+
+ DsdiffChunkHeader chunk_header;
+ /* check if it is is a proper DFF file */
+ if (!dsdiff_read_metadata(decoder, is, &metadata, &chunk_header))
+ return;
+
+ GError *error = nullptr;
+ AudioFormat audio_format;
+ if (!audio_format_init_checked(audio_format, metadata.sample_rate / 8,
+ SampleFormat::DSD,
+ metadata.channels, &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return;
+ }
+
+ /* calculate song time from DSD chunk size and sample frequency */
+ uint64_t chunk_size = metadata.chunk_size;
+ float songtime = ((chunk_size / metadata.channels) * 8) /
+ (float) metadata.sample_rate;
+
+ /* success: file was recognized */
+ decoder_initialized(decoder, audio_format, false, songtime);
+
+ /* every iteration of the following loop decodes one "DSD"
+ chunk from a DFF file */
+
+ while (true) {
+ chunk_size = chunk_header.GetSize();
+
+ if (dsdlib_id_equals(&chunk_header.id, "DSD ")) {
+ if (!dsdiff_decode_chunk(decoder, is,
+ metadata.channels,
+ chunk_size))
+ break;
+ } else {
+ /* ignore other chunks */
+ if (!dsdlib_skip(decoder, is, chunk_size))
+ break;
+ }
+
+ /* read next chunk header; the first one was read by
+ dsdiff_read_metadata() */
+ if (!dsdiff_read_chunk_header(decoder,
+ is, &chunk_header))
+ break;
+ }
+}
+
+static bool
+dsdiff_scan_stream(struct input_stream *is,
+ G_GNUC_UNUSED const struct tag_handler *handler,
+ G_GNUC_UNUSED void *handler_ctx)
+{
+ DsdiffMetaData metadata;
+ DsdiffChunkHeader chunk_header;
+
+ /* First check for DFF metadata */
+ if (!dsdiff_read_metadata(nullptr, is, &metadata, &chunk_header))
+ return false;
+
+ AudioFormat audio_format;
+ if (!audio_format_init_checked(audio_format, metadata.sample_rate / 8,
+ SampleFormat::DSD,
+ metadata.channels, nullptr))
+ /* refuse to parse files which we cannot play anyway */
+ return false;
+
+ /* calculate song time and add as tag */
+ unsigned songtime = ((metadata.chunk_size / metadata.channels) * 8) /
+ metadata.sample_rate;
+ tag_handler_invoke_duration(handler, handler_ctx, songtime);
+
+ /* Read additional metadata and created tags if available */
+ dsdiff_read_metadata_extra(nullptr, is, &metadata, &chunk_header,
+ handler, handler_ctx);
+
+ return true;
+}
+
+static const char *const dsdiff_suffixes[] = {
+ "dff",
+ nullptr
+};
+
+static const char *const dsdiff_mime_types[] = {
+ "application/x-dff",
+ nullptr
+};
+
+const struct decoder_plugin dsdiff_decoder_plugin = {
+ "dsdiff",
+ dsdiff_init,
+ nullptr,
+ dsdiff_stream_decode,
+ nullptr,
+ nullptr,
+ dsdiff_scan_stream,
+ nullptr,
+ dsdiff_suffixes,
+ dsdiff_mime_types,
+};
diff --git a/src/decoder/DsdiffDecoderPlugin.hxx b/src/decoder/DsdiffDecoderPlugin.hxx
new file mode 100644
index 000000000..c50605457
--- /dev/null
+++ b/src/decoder/DsdiffDecoderPlugin.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_DECODER_DSDIFF_H
+#define MPD_DECODER_DSDIFF_H
+
+extern const struct decoder_plugin dsdiff_decoder_plugin;
+
+#endif
diff --git a/src/decoder/DsfDecoderPlugin.cxx b/src/decoder/DsfDecoderPlugin.cxx
new file mode 100644
index 000000000..ad1323d88
--- /dev/null
+++ b/src/decoder/DsfDecoderPlugin.cxx
@@ -0,0 +1,360 @@
+/*
+ * 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
+ *
+ * This plugin decodes DSDIFF data (SACD) embedded in DSF files.
+ *
+ * The DSF code was created using the specification found here:
+ * http://dsd-guide.com/sonys-dsf-file-format-spec
+ *
+ * All functions common to both DSD decoders have been moved to dsdlib
+ */
+
+#include "config.h"
+#include "DsfDecoderPlugin.hxx"
+#include "DecoderAPI.hxx"
+#include "CheckAudioFormat.hxx"
+#include "util/bit_reverse.h"
+#include "DsdLib.hxx"
+#include "TagHandler.hxx"
+
+#include <unistd.h>
+#include <stdio.h> /* for SEEK_SET, SEEK_CUR */
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "dsf"
+
+struct DsfMetaData {
+ unsigned sample_rate, channels;
+ bool bitreverse;
+ uint64_t chunk_size;
+#ifdef HAVE_ID3TAG
+ goffset id3_offset;
+ uint64_t id3_size;
+#endif
+};
+
+struct DsfHeader {
+ /** DSF header id: "DSD " */
+ struct dsdlib_id id;
+ /** DSD chunk size, including id = 28 */
+ uint32_t size_low, size_high;
+ /** total file size */
+ uint32_t fsize_low, fsize_high;
+ /** pointer to id3v2 metadata, should be at the end of the file */
+ uint32_t pmeta_low, pmeta_high;
+};
+
+/** DSF file fmt chunk */
+struct DsfFmtChunk {
+ /** id: "fmt " */
+ struct dsdlib_id id;
+ /** fmt chunk size, including id, normally 52 */
+ uint32_t size_low, size_high;
+ /** version of this format = 1 */
+ uint32_t version;
+ /** 0: DSD raw */
+ uint32_t formatid;
+ /** channel type, 1 = mono, 2 = stereo, 3 = 3 channels, etc */
+ uint32_t channeltype;
+ /** Channel number, 1 = mono, 2 = stereo, ... 6 = 6 channels */
+ uint32_t channelnum;
+ /** sample frequency: 2822400, 5644800 */
+ uint32_t sample_freq;
+ /** bits per sample 1 or 8 */
+ uint32_t bitssample;
+ /** Sample count per channel in bytes */
+ uint32_t scnt_low, scnt_high;
+ /** block size per channel = 4096 */
+ uint32_t block_size;
+ /** reserved, should be all zero */
+ uint32_t reserved;
+};
+
+struct DsfDataChunk {
+ struct dsdlib_id id;
+ /** "data" chunk size, includes header (id+size) */
+ uint32_t size_low, size_high;
+};
+
+/**
+ * Read and parse all needed metadata chunks for DSF files.
+ */
+static bool
+dsf_read_metadata(struct decoder *decoder, struct input_stream *is,
+ DsfMetaData *metadata)
+{
+ uint64_t chunk_size;
+ DsfHeader dsf_header;
+ if (!dsdlib_read(decoder, is, &dsf_header, sizeof(dsf_header)) ||
+ !dsdlib_id_equals(&dsf_header.id, "DSD "))
+ return false;
+
+ chunk_size = (((uint64_t)GUINT32_FROM_LE(dsf_header.size_high)) << 32) |
+ ((uint64_t)GUINT32_FROM_LE(dsf_header.size_low));
+
+ if (sizeof(dsf_header) != chunk_size)
+ return false;
+
+#ifdef HAVE_ID3TAG
+ uint64_t metadata_offset;
+ metadata_offset = (((uint64_t)GUINT32_FROM_LE(dsf_header.pmeta_high)) << 32) |
+ ((uint64_t)GUINT32_FROM_LE(dsf_header.pmeta_low));
+#endif
+
+ /* read the 'fmt ' chunk of the DSF file */
+ DsfFmtChunk dsf_fmt_chunk;
+ if (!dsdlib_read(decoder, is, &dsf_fmt_chunk, sizeof(dsf_fmt_chunk)) ||
+ !dsdlib_id_equals(&dsf_fmt_chunk.id, "fmt "))
+ return false;
+
+ uint64_t fmt_chunk_size;
+ fmt_chunk_size = (((uint64_t)GUINT32_FROM_LE(dsf_fmt_chunk.size_high)) << 32) |
+ ((uint64_t)GUINT32_FROM_LE(dsf_fmt_chunk.size_low));
+
+ if (fmt_chunk_size != sizeof(dsf_fmt_chunk))
+ return false;
+
+ uint32_t samplefreq = (uint32_t)GUINT32_FROM_LE(dsf_fmt_chunk.sample_freq);
+
+ /* for now, only support version 1 of the standard, DSD raw stereo
+ files with a sample freq of 2822400 Hz */
+
+ if (dsf_fmt_chunk.version != 1 || dsf_fmt_chunk.formatid != 0
+ || dsf_fmt_chunk.channeltype != 2
+ || dsf_fmt_chunk.channelnum != 2
+ || samplefreq != 2822400)
+ return false;
+
+ uint32_t chblksize = (uint32_t)GUINT32_FROM_LE(dsf_fmt_chunk.block_size);
+ /* according to the spec block size should always be 4096 */
+ if (chblksize != 4096)
+ return false;
+
+ /* read the 'data' chunk of the DSF file */
+ DsfDataChunk data_chunk;
+ if (!dsdlib_read(decoder, is, &data_chunk, sizeof(data_chunk)) ||
+ !dsdlib_id_equals(&data_chunk.id, "data"))
+ return false;
+
+ /* data size of DSF files are padded to multiple of 4096,
+ we use the actual data size as chunk size */
+
+ uint64_t data_size;
+ data_size = (((uint64_t)GUINT32_FROM_LE(data_chunk.size_high)) << 32) |
+ ((uint64_t)GUINT32_FROM_LE(data_chunk.size_low));
+ data_size -= sizeof(data_chunk);
+
+ metadata->chunk_size = data_size;
+ /* data_size cannot be bigger or equal to total file size */
+ const uint64_t size = (uint64_t)input_stream_get_size(is);
+ if (data_size >= size)
+ return false;
+
+ metadata->channels = (unsigned) dsf_fmt_chunk.channelnum;
+ metadata->sample_rate = samplefreq;
+#ifdef HAVE_ID3TAG
+ /* metada_offset cannot be bigger then or equal to total file size */
+ if (metadata_offset >= size)
+ metadata->id3_offset = 0;
+ else
+ metadata->id3_offset = (goffset) metadata_offset;
+#endif
+ /* check bits per sample format, determine if bitreverse is needed */
+ metadata->bitreverse = dsf_fmt_chunk.bitssample == 1;
+ return true;
+}
+
+static void
+bit_reverse_buffer(uint8_t *p, uint8_t *end)
+{
+ for (; p < end; ++p)
+ *p = bit_reverse(*p);
+}
+
+/**
+ * DSF data is build up of alternating 4096 blocks of DSD samples for left and
+ * right. Convert the buffer holding 1 block of 4096 DSD left samples and 1
+ * block of 4096 DSD right samples to 8k of samples in normal PCM left/right
+ * order.
+ */
+static void
+dsf_to_pcm_order(uint8_t *dest, uint8_t *scratch, size_t nrbytes)
+{
+ for (unsigned i = 0, j = 0; i < (unsigned)nrbytes; i += 2) {
+ scratch[i] = *(dest+j);
+ j++;
+ }
+
+ for (unsigned i = 1, j = 0; i < (unsigned) nrbytes; i += 2) {
+ scratch[i] = *(dest+4096+j);
+ j++;
+ }
+
+ for (unsigned i = 0; i < (unsigned)nrbytes; i++) {
+ *dest = scratch[i];
+ dest++;
+ }
+}
+
+/**
+ * Decode one complete DSF 'data' chunk i.e. a complete song
+ */
+static bool
+dsf_decode_chunk(struct decoder *decoder, struct input_stream *is,
+ unsigned channels,
+ uint64_t chunk_size,
+ bool bitreverse)
+{
+ uint8_t buffer[8192];
+
+ /* scratch buffer for DSF samples to convert to the needed
+ normal left/right regime of samples */
+ uint8_t dsf_scratch_buffer[8192];
+
+ const size_t sample_size = sizeof(buffer[0]);
+ const size_t frame_size = channels * sample_size;
+ const unsigned buffer_frames = sizeof(buffer) / frame_size;
+ const unsigned buffer_samples = buffer_frames * frame_size;
+ const size_t buffer_size = buffer_samples * sample_size;
+
+ while (chunk_size > 0) {
+ /* see how much aligned data from the remaining chunk
+ fits into the local buffer */
+ unsigned now_frames = buffer_frames;
+ size_t now_size = buffer_size;
+ if (chunk_size < (uint64_t)now_size) {
+ now_frames = (unsigned)chunk_size / frame_size;
+ now_size = now_frames * frame_size;
+ }
+
+ size_t nbytes = decoder_read(decoder, is, buffer, now_size);
+ if (nbytes != now_size)
+ return false;
+
+ chunk_size -= nbytes;
+
+ if (bitreverse)
+ bit_reverse_buffer(buffer, buffer + nbytes);
+
+ dsf_to_pcm_order(buffer, dsf_scratch_buffer, nbytes);
+
+ enum decoder_command cmd =
+ decoder_data(decoder, is, buffer, nbytes, 0);
+ switch (cmd) {
+ case DECODE_COMMAND_NONE:
+ break;
+
+ case DECODE_COMMAND_START:
+ case DECODE_COMMAND_STOP:
+ return false;
+
+ case DECODE_COMMAND_SEEK:
+
+ /* not implemented yet */
+ decoder_seek_error(decoder);
+ break;
+ }
+ }
+ return dsdlib_skip(decoder, is, chunk_size);
+}
+
+static void
+dsf_stream_decode(struct decoder *decoder, struct input_stream *is)
+{
+ /* check if it is a proper DSF file */
+ DsfMetaData metadata;
+ if (!dsf_read_metadata(decoder, is, &metadata))
+ return;
+
+ GError *error = NULL;
+ AudioFormat audio_format;
+ if (!audio_format_init_checked(audio_format, metadata.sample_rate / 8,
+ SampleFormat::DSD,
+ metadata.channels, &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return;
+ }
+ /* Calculate song time from DSD chunk size and sample frequency */
+ uint64_t chunk_size = metadata.chunk_size;
+ float songtime = ((chunk_size / metadata.channels) * 8) /
+ (float) metadata.sample_rate;
+
+ /* success: file was recognized */
+ decoder_initialized(decoder, audio_format, false, songtime);
+
+ if (!dsf_decode_chunk(decoder, is, metadata.channels,
+ chunk_size,
+ metadata.bitreverse))
+ return;
+}
+
+static bool
+dsf_scan_stream(struct input_stream *is,
+ G_GNUC_UNUSED const struct tag_handler *handler,
+ G_GNUC_UNUSED void *handler_ctx)
+{
+ /* check DSF metadata */
+ DsfMetaData metadata;
+ if (!dsf_read_metadata(NULL, is, &metadata))
+ return false;
+
+ AudioFormat audio_format;
+ if (!audio_format_init_checked(audio_format, metadata.sample_rate / 8,
+ SampleFormat::DSD,
+ metadata.channels, NULL))
+ /* refuse to parse files which we cannot play anyway */
+ return false;
+
+ /* calculate song time and add as tag */
+ unsigned songtime = ((metadata.chunk_size / metadata.channels) * 8) /
+ metadata.sample_rate;
+ tag_handler_invoke_duration(handler, handler_ctx, songtime);
+
+#ifdef HAVE_ID3TAG
+ /* Add available tags from the ID3 tag */
+ dsdlib_tag_id3(is, handler, handler_ctx, metadata.id3_offset);
+#endif
+ return true;
+}
+
+static const char *const dsf_suffixes[] = {
+ "dsf",
+ NULL
+};
+
+static const char *const dsf_mime_types[] = {
+ "application/x-dsf",
+ NULL
+};
+
+const struct decoder_plugin dsf_decoder_plugin = {
+ "dsf",
+ nullptr,
+ nullptr,
+ dsf_stream_decode,
+ nullptr,
+ nullptr,
+ dsf_scan_stream,
+ nullptr,
+ dsf_suffixes,
+ dsf_mime_types,
+};
diff --git a/src/decoder/DsfDecoderPlugin.hxx b/src/decoder/DsfDecoderPlugin.hxx
new file mode 100644
index 000000000..749032d1f
--- /dev/null
+++ b/src/decoder/DsfDecoderPlugin.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_DECODER_DSF_H
+#define MPD_DECODER_DSF_H
+
+extern const struct decoder_plugin dsf_decoder_plugin;
+
+#endif
diff --git a/src/decoder/FaadDecoderPlugin.cxx b/src/decoder/FaadDecoderPlugin.cxx
new file mode 100644
index 000000000..547ba24e0
--- /dev/null
+++ b/src/decoder/FaadDecoderPlugin.cxx
@@ -0,0 +1,503 @@
+/*
+ * 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 "FaadDecoderPlugin.hxx"
+#include "DecoderAPI.hxx"
+#include "DecoderBuffer.hxx"
+#include "CheckAudioFormat.hxx"
+#include "TagHandler.hxx"
+
+#include <neaacdec.h>
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+#include <unistd.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "faad"
+
+#define AAC_MAX_CHANNELS 6
+
+static const unsigned adts_sample_rates[] =
+ { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050,
+ 16000, 12000, 11025, 8000, 7350, 0, 0, 0
+};
+
+/**
+ * The GLib quark used for errors reported by this plugin.
+ */
+static inline GQuark
+faad_decoder_quark(void)
+{
+ return g_quark_from_static_string("faad");
+}
+
+/**
+ * Check whether the buffer head is an AAC frame, and return the frame
+ * length. Returns 0 if it is not a frame.
+ */
+static size_t
+adts_check_frame(const unsigned char *data)
+{
+ /* check syncword */
+ if (!((data[0] == 0xFF) && ((data[1] & 0xF6) == 0xF0)))
+ return 0;
+
+ return (((unsigned int)data[3] & 0x3) << 11) |
+ (((unsigned int)data[4]) << 3) |
+ (data[5] >> 5);
+}
+
+/**
+ * Find the next AAC frame in the buffer. Returns 0 if no frame is
+ * found or if not enough data is available.
+ */
+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) {
+ /* not enough data yet */
+ ret = decoder_buffer_fill(buffer);
+ if (!ret)
+ /* failed */
+ return 0;
+
+ continue;
+ }
+
+ /* find the 0xff marker */
+ const uint8_t *p = (const uint8_t *)memchr(data, 0xff, length);
+ if (p == nullptr) {
+ /* no marker - discard the buffer */
+ decoder_buffer_consume(buffer, length);
+ continue;
+ }
+
+ if (p > data) {
+ /* discard data before 0xff */
+ decoder_buffer_consume(buffer, p - data);
+ continue;
+ }
+
+ /* is it a frame? */
+ frame_length = adts_check_frame(data);
+ if (frame_length == 0) {
+ /* it's just some random 0xff byte; discard it
+ and continue searching */
+ decoder_buffer_consume(buffer, 1);
+ continue;
+ }
+
+ if (length < frame_length) {
+ /* available buffer size is smaller than the
+ frame will be - attempt to read more
+ data */
+ ret = decoder_buffer_fill(buffer);
+ if (!ret) {
+ /* 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);
+ }
+
+ continue;
+ }
+
+ /* found a full frame! */
+ return frame_length;
+ }
+}
+
+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);
+ 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);
+
+ sample_rate = adts_sample_rates[(data[2] & 0x3c) >> 2];
+ }
+
+ decoder_buffer_consume(buffer, frame_length);
+ }
+
+ frames_per_second = (float)sample_rate / 1024.0;
+ if (frames_per_second <= 0)
+ return -1;
+
+ return (float)frames / frames_per_second;
+}
+
+static float
+faad_song_duration(DecoderBuffer *buffer, struct input_stream *is)
+{
+ size_t fileread;
+ size_t tagsize;
+ size_t length;
+ bool success;
+
+ const goffset size = input_stream_get_size(is);
+ fileread = size >= 0 ? size : 0;
+
+ decoder_buffer_fill(buffer);
+ const uint8_t *data = (const uint8_t *)
+ decoder_buffer_read(buffer, &length);
+ if (data == nullptr)
+ return -1;
+
+ tagsize = 0;
+ if (length >= 10 && !memcmp(data, "ID3", 3)) {
+ /* skip the ID3 tag */
+
+ tagsize = (data[6] << 21) | (data[7] << 14) |
+ (data[8] << 7) | (data[9] << 0);
+
+ tagsize += 10;
+
+ 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)
+ return -1;
+ }
+
+ if (input_stream_is_seekable(is) && length >= 2 &&
+ data[0] == 0xFF && ((data[1] & 0xF6) == 0xF0)) {
+ /* obtain the duration from the ADTS header */
+ float song_length = adts_song_duration(buffer);
+
+ input_stream_lock_seek(is, tagsize, SEEK_SET, nullptr);
+
+ data = (const uint8_t *)decoder_buffer_read(buffer, &length);
+ if (data != nullptr)
+ decoder_buffer_consume(buffer, length);
+ decoder_buffer_fill(buffer);
+
+ return song_length;
+ } else if (length >= 5 && memcmp(data, "ADIF", 4) == 0) {
+ /* obtain the duration from the ADIF header */
+ unsigned bit_rate;
+ size_t skip_size = (data[4] & 0x80) ? 9 : 0;
+
+ if (8 + skip_size > length)
+ /* 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);
+
+ if (fileread != 0 && bit_rate != 0)
+ return fileread * 8.0 / bit_rate;
+ else
+ return fileread;
+ } else
+ return -1;
+}
+
+/**
+ * Wrapper for NeAACDecInit() which works around some API
+ * inconsistencies in libfaad.
+ */
+static bool
+faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer *buffer,
+ AudioFormat &audio_format, GError **error_r)
+{
+ 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
+ warnings, use this workaround. */
+ unsigned long *sample_rate_p = (unsigned long *)(void *)&sample_rate;
+#else
+ 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) {
+ g_set_error(error_r, faad_decoder_quark(), 0,
+ "Empty file");
+ return false;
+ }
+
+ nbytes = NeAACDecInit(decoder,
+ /* deconst hack, libfaad requires this */
+ const_cast<unsigned char *>(data),
+ length,
+ sample_rate_p, &channels);
+ if (nbytes < 0) {
+ g_set_error(error_r, faad_decoder_quark(), 0,
+ "Not an AAC stream");
+ return false;
+ }
+
+ decoder_buffer_consume(buffer, nbytes);
+
+ return audio_format_init_checked(audio_format, sample_rate,
+ SampleFormat::S16, channels, error_r);
+}
+
+/**
+ * Wrapper for NeAACDecDecode() which works around some API
+ * inconsistencies in libfaad.
+ */
+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)
+ return nullptr;
+
+ return NeAACDecDecode(decoder, frame_info,
+ /* deconst hack, libfaad requires this */
+ const_cast<unsigned char *>(data),
+ length);
+}
+
+/**
+ * Get a song file's total playing time in seconds, as a float.
+ * Returns 0 if the duration is unknown, and a negative value if the
+ * file is invalid.
+ */
+static float
+faad_get_file_time_float(struct input_stream *is)
+{
+ DecoderBuffer *buffer;
+ float length;
+
+ buffer = decoder_buffer_new(nullptr, is,
+ FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS);
+ length = faad_song_duration(buffer, is);
+
+ if (length < 0) {
+ bool ret;
+ AudioFormat audio_format;
+
+ NeAACDecHandle decoder = NeAACDecOpen();
+
+ NeAACDecConfigurationPtr config =
+ NeAACDecGetCurrentConfiguration(decoder);
+ config->outputFormat = FAAD_FMT_16BIT;
+ NeAACDecSetConfiguration(decoder, config);
+
+ decoder_buffer_fill(buffer);
+
+ ret = faad_decoder_init(decoder, buffer, audio_format, nullptr);
+ if (ret)
+ length = 0;
+
+ NeAACDecClose(decoder);
+ }
+
+ decoder_buffer_free(buffer);
+
+ return length;
+}
+
+/**
+ * Get a song file's total playing time in seconds, as an int.
+ * Returns 0 if the duration is unknown, and a negative value if the
+ * file is invalid.
+ */
+static int
+faad_get_file_time(struct input_stream *is)
+{
+ int file_time = -1;
+ float length;
+
+ if ((length = faad_get_file_time_float(is)) >= 0)
+ file_time = length + 0.5;
+
+ return file_time;
+}
+
+static void
+faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is)
+{
+ GError *error = nullptr;
+ float total_time = 0;
+ AudioFormat audio_format;
+ bool ret;
+ uint16_t bit_rate = 0;
+ DecoderBuffer *buffer;
+ enum decoder_command cmd;
+
+ buffer = decoder_buffer_new(mpd_decoder, is,
+ FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS);
+ total_time = faad_song_duration(buffer, is);
+
+ /* create the libfaad decoder */
+
+ NeAACDecHandle decoder = NeAACDecOpen();
+
+ NeAACDecConfigurationPtr config =
+ NeAACDecGetCurrentConfiguration(decoder);
+ config->outputFormat = FAAD_FMT_16BIT;
+ config->downMatrix = 1;
+ config->dontUpSampleImplicitSBR = 0;
+ NeAACDecSetConfiguration(decoder, config);
+
+ while (!decoder_buffer_is_full(buffer) &&
+ !input_stream_lock_eof(is) &&
+ decoder_get_command(mpd_decoder) == DECODE_COMMAND_NONE) {
+ adts_find_frame(buffer);
+ decoder_buffer_fill(buffer);
+ }
+
+ /* initialize it */
+
+ ret = faad_decoder_init(decoder, buffer, audio_format, &error);
+ if (!ret) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ NeAACDecClose(decoder);
+ return;
+ }
+
+ /* initialize the MPD core */
+
+ decoder_initialized(mpd_decoder, audio_format, false, total_time);
+
+ /* the decoder loop */
+
+ do {
+ size_t frame_size;
+ const void *decoded;
+ NeAACDecFrameInfo frame_info;
+
+ /* find the next frame */
+
+ frame_size = adts_find_frame(buffer);
+ if (frame_size == 0)
+ /* end of file */
+ break;
+
+ /* decode it */
+
+ decoded = faad_decoder_decode(decoder, buffer, &frame_info);
+
+ if (frame_info.error > 0) {
+ g_warning("error decoding AAC stream: %s\n",
+ NeAACDecGetErrorMessage(frame_info.error));
+ break;
+ }
+
+ if (frame_info.channels != audio_format.channels) {
+ g_warning("channel count changed from %u to %u",
+ audio_format.channels, frame_info.channels);
+ break;
+ }
+
+ if (frame_info.samplerate != audio_format.sample_rate) {
+ g_warning("sample rate changed from %u to %lu",
+ audio_format.sample_rate,
+ (unsigned long)frame_info.samplerate);
+ break;
+ }
+
+ decoder_buffer_consume(buffer, frame_info.bytesconsumed);
+
+ /* update bit rate and position */
+
+ if (frame_info.samples > 0) {
+ bit_rate = frame_info.bytesconsumed * 8.0 *
+ frame_info.channels * audio_format.sample_rate /
+ frame_info.samples / 1000 + 0.5;
+ }
+
+ /* send PCM samples to MPD */
+
+ cmd = decoder_data(mpd_decoder, is, decoded,
+ (size_t)frame_info.samples * 2,
+ bit_rate);
+ } while (cmd != DECODE_COMMAND_STOP);
+
+ /* cleanup */
+
+ NeAACDecClose(decoder);
+}
+
+static bool
+faad_scan_stream(struct input_stream *is,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ int file_time = faad_get_file_time(is);
+
+ if (file_time < 0)
+ return false;
+
+ tag_handler_invoke_duration(handler, handler_ctx, file_time);
+ return true;
+}
+
+static const char *const faad_suffixes[] = { "aac", nullptr };
+static const char *const faad_mime_types[] = {
+ "audio/aac", "audio/aacp", nullptr
+};
+
+const struct decoder_plugin faad_decoder_plugin = {
+ "faad",
+ nullptr,
+ nullptr,
+ faad_stream_decode,
+ nullptr,
+ nullptr,
+ faad_scan_stream,
+ nullptr,
+ faad_suffixes,
+ faad_mime_types,
+};
diff --git a/src/decoder/FaadDecoderPlugin.hxx b/src/decoder/FaadDecoderPlugin.hxx
new file mode 100644
index 000000000..162c155ad
--- /dev/null
+++ b/src/decoder/FaadDecoderPlugin.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_FAAD_DECODER_PLUGIN_HXX
+#define MPD_FAAD_DECODER_PLUGIN_HXX
+
+extern const struct decoder_plugin faad_decoder_plugin;
+
+#endif
diff --git a/src/decoder/FfmpegDecoderPlugin.cxx b/src/decoder/FfmpegDecoderPlugin.cxx
new file mode 100644
index 000000000..89e57c874
--- /dev/null
+++ b/src/decoder/FfmpegDecoderPlugin.cxx
@@ -0,0 +1,687 @@
+/*
+ * 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.
+ */
+
+/* necessary because libavutil/common.h uses UINT64_C */
+#define __STDC_CONSTANT_MACROS
+
+#include "config.h"
+#include "FfmpegDecoderPlugin.hxx"
+#include "DecoderAPI.hxx"
+#include "FfmpegMetaData.hxx"
+#include "TagHandler.hxx"
+#include "InputStream.hxx"
+#include "CheckAudioFormat.hxx"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+extern "C" {
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+#include <libavformat/avio.h>
+#include <libavutil/avutil.h>
+#include <libavutil/log.h>
+#include <libavutil/mathematics.h>
+#include <libavutil/dict.h>
+}
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "ffmpeg"
+
+/* suppress the ffmpeg compatibility macro */
+#ifdef SampleFormat
+#undef SampleFormat
+#endif
+
+static GLogLevelFlags
+level_ffmpeg_to_glib(int level)
+{
+ if (level <= AV_LOG_FATAL)
+ return G_LOG_LEVEL_CRITICAL;
+
+ if (level <= AV_LOG_ERROR)
+ return G_LOG_LEVEL_WARNING;
+
+ if (level <= AV_LOG_INFO)
+ return G_LOG_LEVEL_MESSAGE;
+
+ return G_LOG_LEVEL_DEBUG;
+}
+
+static void
+mpd_ffmpeg_log_callback(G_GNUC_UNUSED void *ptr, int level,
+ const char *fmt, va_list vl)
+{
+ const AVClass * cls = NULL;
+
+ if (ptr != NULL)
+ cls = *(const AVClass *const*)ptr;
+
+ if (cls != NULL) {
+ char *domain = g_strconcat(G_LOG_DOMAIN, "/", cls->item_name(ptr), NULL);
+ g_logv(domain, level_ffmpeg_to_glib(level), fmt, vl);
+ g_free(domain);
+ }
+}
+
+struct mpd_ffmpeg_stream {
+ struct decoder *decoder;
+ struct input_stream *input;
+
+ AVIOContext *io;
+
+ unsigned char buffer[8192];
+};
+
+static int
+mpd_ffmpeg_stream_read(void *opaque, uint8_t *buf, int size)
+{
+ struct mpd_ffmpeg_stream *stream = (struct mpd_ffmpeg_stream *)opaque;
+
+ return decoder_read(stream->decoder, stream->input,
+ (void *)buf, size);
+}
+
+static int64_t
+mpd_ffmpeg_stream_seek(void *opaque, int64_t pos, int whence)
+{
+ struct mpd_ffmpeg_stream *stream = (struct mpd_ffmpeg_stream *)opaque;
+
+ if (whence == AVSEEK_SIZE)
+ return stream->input->size;
+
+ if (!input_stream_lock_seek(stream->input, pos, whence, NULL))
+ return -1;
+
+ return stream->input->offset;
+}
+
+static struct mpd_ffmpeg_stream *
+mpd_ffmpeg_stream_open(struct decoder *decoder, struct input_stream *input)
+{
+ struct mpd_ffmpeg_stream *stream = g_new(struct mpd_ffmpeg_stream, 1);
+ stream->decoder = decoder;
+ stream->input = input;
+ stream->io = avio_alloc_context(stream->buffer, sizeof(stream->buffer),
+ false, stream,
+ mpd_ffmpeg_stream_read, NULL,
+ input->seekable
+ ? mpd_ffmpeg_stream_seek : NULL);
+ if (stream->io == NULL) {
+ g_free(stream);
+ return NULL;
+ }
+
+ return stream;
+}
+
+/**
+ * API compatibility wrapper for av_open_input_stream() and
+ * avformat_open_input().
+ */
+static int
+mpd_ffmpeg_open_input(AVFormatContext **ic_ptr,
+ AVIOContext *pb,
+ const char *filename,
+ AVInputFormat *fmt)
+{
+ AVFormatContext *context = avformat_alloc_context();
+ if (context == NULL)
+ return AVERROR(ENOMEM);
+
+ context->pb = pb;
+ *ic_ptr = context;
+ return avformat_open_input(ic_ptr, filename, fmt, NULL);
+}
+
+static void
+mpd_ffmpeg_stream_close(struct mpd_ffmpeg_stream *stream)
+{
+ av_free(stream->io);
+ g_free(stream);
+}
+
+static bool
+ffmpeg_init(gcc_unused const config_param &param)
+{
+ av_log_set_callback(mpd_ffmpeg_log_callback);
+
+ av_register_all();
+ return true;
+}
+
+static int
+ffmpeg_find_audio_stream(const AVFormatContext *format_context)
+{
+ for (unsigned i = 0; i < format_context->nb_streams; ++i)
+ if (format_context->streams[i]->codec->codec_type ==
+ AVMEDIA_TYPE_AUDIO)
+ return i;
+
+ return -1;
+}
+
+G_GNUC_CONST
+static double
+time_from_ffmpeg(int64_t t, const AVRational time_base)
+{
+ assert(t != (int64_t)AV_NOPTS_VALUE);
+
+ return (double)av_rescale_q(t, time_base, (AVRational){1, 1024})
+ / (double)1024;
+}
+
+G_GNUC_CONST
+static int64_t
+time_to_ffmpeg(double t, const AVRational time_base)
+{
+ return av_rescale_q((int64_t)(t * 1024), (AVRational){1, 1024},
+ time_base);
+}
+
+static void
+copy_interleave_frame2(uint8_t *dest, uint8_t **src,
+ unsigned nframes, unsigned nchannels,
+ unsigned sample_size)
+{
+ for (unsigned frame = 0; frame < nframes; ++frame) {
+ for (unsigned channel = 0; channel < nchannels; ++channel) {
+ memcpy(dest, src[channel] + frame * sample_size,
+ sample_size);
+ dest += sample_size;
+ }
+ }
+}
+
+/**
+ * Copy PCM data from a AVFrame to an interleaved buffer.
+ */
+static int
+copy_interleave_frame(const AVCodecContext *codec_context,
+ const AVFrame *frame,
+ uint8_t *buffer, size_t buffer_size)
+{
+ int plane_size;
+ const int data_size =
+ av_samples_get_buffer_size(&plane_size,
+ codec_context->channels,
+ frame->nb_samples,
+ codec_context->sample_fmt, 1);
+ if (buffer_size < (size_t)data_size)
+ /* buffer is too small - shouldn't happen */
+ return AVERROR(EINVAL);
+
+ if (av_sample_fmt_is_planar(codec_context->sample_fmt) &&
+ codec_context->channels > 1) {
+ copy_interleave_frame2(buffer, frame->extended_data,
+ frame->nb_samples,
+ codec_context->channels,
+ av_get_bytes_per_sample(codec_context->sample_fmt));
+ } else {
+ memcpy(buffer, frame->extended_data[0], data_size);
+ }
+
+ return data_size;
+}
+
+static enum decoder_command
+ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is,
+ const AVPacket *packet,
+ AVCodecContext *codec_context,
+ const AVRational *time_base,
+ AVFrame *frame)
+{
+ if (packet->pts >= 0 && packet->pts != (int64_t)AV_NOPTS_VALUE)
+ decoder_timestamp(decoder,
+ time_from_ffmpeg(packet->pts, *time_base));
+
+ AVPacket packet2 = *packet;
+
+ uint8_t aligned_buffer[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2 + 16];
+ const size_t buffer_size = sizeof(aligned_buffer);
+
+ enum decoder_command cmd = DECODE_COMMAND_NONE;
+ while (packet2.size > 0 &&
+ cmd == DECODE_COMMAND_NONE) {
+ int audio_size = buffer_size;
+ int got_frame = 0;
+ int len = avcodec_decode_audio4(codec_context,
+ frame, &got_frame,
+ &packet2);
+ if (len >= 0 && got_frame) {
+ audio_size = copy_interleave_frame(codec_context,
+ frame,
+ aligned_buffer,
+ buffer_size);
+ if (audio_size < 0)
+ len = audio_size;
+ } else if (len >= 0)
+ len = -1;
+
+ if (len < 0) {
+ /* if error, we skip the frame */
+ g_message("decoding failed, frame skipped\n");
+ break;
+ }
+
+ packet2.data += len;
+ packet2.size -= len;
+
+ if (audio_size <= 0)
+ continue;
+
+ cmd = decoder_data(decoder, is,
+ aligned_buffer, audio_size,
+ codec_context->bit_rate / 1000);
+ }
+ return cmd;
+}
+
+G_GNUC_CONST
+static SampleFormat
+ffmpeg_sample_format(enum AVSampleFormat sample_fmt)
+{
+ switch (sample_fmt) {
+ case AV_SAMPLE_FMT_S16:
+ case AV_SAMPLE_FMT_S16P:
+ return SampleFormat::S16;
+
+ case AV_SAMPLE_FMT_S32:
+ case AV_SAMPLE_FMT_S32P:
+ return SampleFormat::S32;
+
+ case AV_SAMPLE_FMT_FLTP:
+ return SampleFormat::FLOAT;
+
+ default:
+ break;
+ }
+
+ char buffer[64];
+ const char *name = av_get_sample_fmt_string(buffer, sizeof(buffer),
+ sample_fmt);
+ if (name != NULL)
+ g_warning("Unsupported libavcodec SampleFormat value: %s (%d)",
+ name, sample_fmt);
+ else
+ g_warning("Unsupported libavcodec SampleFormat value: %d",
+ sample_fmt);
+ return SampleFormat::UNDEFINED;
+}
+
+static AVInputFormat *
+ffmpeg_probe(struct decoder *decoder, struct input_stream *is)
+{
+ enum {
+ BUFFER_SIZE = 16384,
+ PADDING = 16,
+ };
+
+ unsigned char *buffer = (unsigned char *)g_malloc(BUFFER_SIZE);
+ size_t nbytes = decoder_read(decoder, is, buffer, BUFFER_SIZE);
+ if (nbytes <= PADDING ||
+ !input_stream_lock_seek(is, 0, SEEK_SET, NULL)) {
+ g_free(buffer);
+ return NULL;
+ }
+
+ /* some ffmpeg parsers (e.g. ac3_parser.c) read a few bytes
+ beyond the declared buffer limit, which makes valgrind
+ angry; this workaround removes some padding from the buffer
+ size */
+ nbytes -= PADDING;
+
+ AVProbeData avpd;
+ avpd.buf = buffer;
+ avpd.buf_size = nbytes;
+ avpd.filename = is->uri.c_str();
+
+ AVInputFormat *format = av_probe_input_format(&avpd, true);
+ g_free(buffer);
+
+ return format;
+}
+
+static void
+ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
+{
+ AVInputFormat *input_format = ffmpeg_probe(decoder, input);
+ if (input_format == NULL)
+ return;
+
+ g_debug("detected input format '%s' (%s)",
+ input_format->name, input_format->long_name);
+
+ struct mpd_ffmpeg_stream *stream =
+ mpd_ffmpeg_stream_open(decoder, input);
+ if (stream == NULL) {
+ g_warning("Failed to open stream");
+ return;
+ }
+
+ //ffmpeg works with ours "fileops" helper
+ AVFormatContext *format_context = NULL;
+ if (mpd_ffmpeg_open_input(&format_context, stream->io,
+ input->uri.c_str(),
+ input_format) != 0) {
+ g_warning("Open failed\n");
+ mpd_ffmpeg_stream_close(stream);
+ return;
+ }
+
+ const int find_result =
+ avformat_find_stream_info(format_context, NULL);
+ if (find_result < 0) {
+ g_warning("Couldn't find stream info\n");
+ avformat_close_input(&format_context);
+ mpd_ffmpeg_stream_close(stream);
+ return;
+ }
+
+ int audio_stream = ffmpeg_find_audio_stream(format_context);
+ if (audio_stream == -1) {
+ g_warning("No audio stream inside\n");
+ avformat_close_input(&format_context);
+ mpd_ffmpeg_stream_close(stream);
+ return;
+ }
+
+ AVStream *av_stream = format_context->streams[audio_stream];
+
+ AVCodecContext *codec_context = av_stream->codec;
+ if (codec_context->codec_name[0] != 0)
+ g_debug("codec '%s'", codec_context->codec_name);
+
+ AVCodec *codec = avcodec_find_decoder(codec_context->codec_id);
+
+ if (!codec) {
+ g_warning("Unsupported audio codec\n");
+ avformat_close_input(&format_context);
+ mpd_ffmpeg_stream_close(stream);
+ return;
+ }
+
+ const SampleFormat sample_format =
+ ffmpeg_sample_format(codec_context->sample_fmt);
+ if (sample_format == SampleFormat::UNDEFINED)
+ return;
+
+ GError *error = NULL;
+ AudioFormat audio_format;
+ if (!audio_format_init_checked(audio_format,
+ codec_context->sample_rate,
+ sample_format,
+ codec_context->channels, &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ avformat_close_input(&format_context);
+ mpd_ffmpeg_stream_close(stream);
+ return;
+ }
+
+ /* the audio format must be read from AVCodecContext by now,
+ because avcodec_open() has been demonstrated to fill bogus
+ values into AVCodecContext.channels - a change that will be
+ reverted later by avcodec_decode_audio3() */
+
+ const int open_result = avcodec_open2(codec_context, codec, NULL);
+ if (open_result < 0) {
+ g_warning("Could not open codec\n");
+ avformat_close_input(&format_context);
+ mpd_ffmpeg_stream_close(stream);
+ return;
+ }
+
+ int total_time = format_context->duration != (int64_t)AV_NOPTS_VALUE
+ ? format_context->duration / AV_TIME_BASE
+ : 0;
+
+ decoder_initialized(decoder, audio_format,
+ input->seekable, total_time);
+
+ AVFrame *frame = avcodec_alloc_frame();
+ if (!frame) {
+ g_warning("Could not allocate frame\n");
+ avformat_close_input(&format_context);
+ mpd_ffmpeg_stream_close(stream);
+ return;
+ }
+
+ enum decoder_command cmd;
+ do {
+ AVPacket packet;
+ if (av_read_frame(format_context, &packet) < 0)
+ /* end of file */
+ break;
+
+ if (packet.stream_index == audio_stream)
+ cmd = ffmpeg_send_packet(decoder, input,
+ &packet, codec_context,
+ &av_stream->time_base,
+ frame);
+ else
+ cmd = decoder_get_command(decoder);
+
+ av_free_packet(&packet);
+
+ if (cmd == DECODE_COMMAND_SEEK) {
+ int64_t where =
+ time_to_ffmpeg(decoder_seek_where(decoder),
+ av_stream->time_base);
+
+ if (av_seek_frame(format_context, audio_stream, where,
+ AV_TIME_BASE) < 0)
+ decoder_seek_error(decoder);
+ else {
+ avcodec_flush_buffers(codec_context);
+ decoder_command_finished(decoder);
+ }
+ }
+ } while (cmd != DECODE_COMMAND_STOP);
+
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 28, 0)
+ avcodec_free_frame(&frame);
+#else
+ av_freep(&frame);
+#endif
+
+ avcodec_close(codec_context);
+ avformat_close_input(&format_context);
+ mpd_ffmpeg_stream_close(stream);
+}
+
+//no tag reading in ffmpeg, check if playable
+static bool
+ffmpeg_scan_stream(struct input_stream *is,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ AVInputFormat *input_format = ffmpeg_probe(NULL, is);
+ if (input_format == NULL)
+ return false;
+
+ struct mpd_ffmpeg_stream *stream = mpd_ffmpeg_stream_open(NULL, is);
+ if (stream == NULL)
+ return false;
+
+ AVFormatContext *f = NULL;
+ if (mpd_ffmpeg_open_input(&f, stream->io, is->uri.c_str(),
+ input_format) != 0) {
+ mpd_ffmpeg_stream_close(stream);
+ return false;
+ }
+
+ const int find_result =
+ avformat_find_stream_info(f, NULL);
+ if (find_result < 0) {
+ avformat_close_input(&f);
+ mpd_ffmpeg_stream_close(stream);
+ return false;
+ }
+
+ if (f->duration != (int64_t)AV_NOPTS_VALUE)
+ tag_handler_invoke_duration(handler, handler_ctx,
+ f->duration / AV_TIME_BASE);
+
+ ffmpeg_scan_dictionary(f->metadata, handler, handler_ctx);
+ int idx = ffmpeg_find_audio_stream(f);
+ if (idx >= 0)
+ ffmpeg_scan_dictionary(f->streams[idx]->metadata,
+ handler, handler_ctx);
+
+ avformat_close_input(&f);
+ mpd_ffmpeg_stream_close(stream);
+
+ return true;
+}
+
+/**
+ * A list of extensions found for the formats supported by ffmpeg.
+ * This list is current as of 02-23-09; To find out if there are more
+ * supported formats, check the ffmpeg changelog since this date for
+ * more formats.
+ */
+static const char *const ffmpeg_suffixes[] = {
+ "16sv", "3g2", "3gp", "4xm", "8svx", "aa3", "aac", "ac3", "afc", "aif",
+ "aifc", "aiff", "al", "alaw", "amr", "anim", "apc", "ape", "asf",
+ "atrac", "au", "aud", "avi", "avm2", "avs", "bap", "bfi", "c93", "cak",
+ "cin", "cmv", "cpk", "daud", "dct", "divx", "dts", "dv", "dvd", "dxa",
+ "eac3", "film", "flac", "flc", "fli", "fll", "flx", "flv", "g726",
+ "gsm", "gxf", "iss", "m1v", "m2v", "m2t", "m2ts",
+ "m4a", "m4b", "m4v",
+ "mad",
+ "mj2", "mjpeg", "mjpg", "mka", "mkv", "mlp", "mm", "mmf", "mov", "mp+",
+ "mp1", "mp2", "mp3", "mp4", "mpc", "mpeg", "mpg", "mpga", "mpp", "mpu",
+ "mve", "mvi", "mxf", "nc", "nsv", "nut", "nuv", "oga", "ogm", "ogv",
+ "ogx", "oma", "ogg", "omg", "psp", "pva", "qcp", "qt", "r3d", "ra",
+ "ram", "rl2", "rm", "rmvb", "roq", "rpl", "rvc", "shn", "smk", "snd",
+ "sol", "son", "spx", "str", "swf", "tgi", "tgq", "tgv", "thp", "ts",
+ "tsp", "tta", "xa", "xvid", "uv", "uv2", "vb", "vid", "vob", "voc",
+ "vp6", "vmd", "wav", "webm", "wma", "wmv", "wsaud", "wsvga", "wv",
+ "wve",
+ NULL
+};
+
+static const char *const ffmpeg_mime_types[] = {
+ "application/flv",
+ "application/m4a",
+ "application/mp4",
+ "application/octet-stream",
+ "application/ogg",
+ "application/x-ms-wmz",
+ "application/x-ms-wmd",
+ "application/x-ogg",
+ "application/x-shockwave-flash",
+ "application/x-shorten",
+ "audio/8svx",
+ "audio/16sv",
+ "audio/aac",
+ "audio/ac3",
+ "audio/aiff"
+ "audio/amr",
+ "audio/basic",
+ "audio/flac",
+ "audio/m4a",
+ "audio/mp4",
+ "audio/mpeg",
+ "audio/musepack",
+ "audio/ogg",
+ "audio/qcelp",
+ "audio/vorbis",
+ "audio/vorbis+ogg",
+ "audio/x-8svx",
+ "audio/x-16sv",
+ "audio/x-aac",
+ "audio/x-ac3",
+ "audio/x-aiff"
+ "audio/x-alaw",
+ "audio/x-au",
+ "audio/x-dca",
+ "audio/x-eac3",
+ "audio/x-flac",
+ "audio/x-gsm",
+ "audio/x-mace",
+ "audio/x-matroska",
+ "audio/x-monkeys-audio",
+ "audio/x-mpeg",
+ "audio/x-ms-wma",
+ "audio/x-ms-wax",
+ "audio/x-musepack",
+ "audio/x-ogg",
+ "audio/x-vorbis",
+ "audio/x-vorbis+ogg",
+ "audio/x-pn-realaudio",
+ "audio/x-pn-multirate-realaudio",
+ "audio/x-speex",
+ "audio/x-tta"
+ "audio/x-voc",
+ "audio/x-wav",
+ "audio/x-wma",
+ "audio/x-wv",
+ "video/anim",
+ "video/quicktime",
+ "video/msvideo",
+ "video/ogg",
+ "video/theora",
+ "video/webm",
+ "video/x-dv",
+ "video/x-flv",
+ "video/x-matroska",
+ "video/x-mjpeg",
+ "video/x-mpeg",
+ "video/x-ms-asf",
+ "video/x-msvideo",
+ "video/x-ms-wmv",
+ "video/x-ms-wvx",
+ "video/x-ms-wm",
+ "video/x-ms-wmx",
+ "video/x-nut",
+ "video/x-pva",
+ "video/x-theora",
+ "video/x-vid",
+ "video/x-wmv",
+ "video/x-xvid",
+
+ /* special value for the "ffmpeg" input plugin: all streams by
+ the "ffmpeg" input plugin shall be decoded by this
+ plugin */
+ "audio/x-mpd-ffmpeg",
+
+ NULL
+};
+
+const struct decoder_plugin ffmpeg_decoder_plugin = {
+ "ffmpeg",
+ ffmpeg_init,
+ nullptr,
+ ffmpeg_decode,
+ nullptr,
+ nullptr,
+ ffmpeg_scan_stream,
+ nullptr,
+ ffmpeg_suffixes,
+ ffmpeg_mime_types
+};
diff --git a/src/decoder/FfmpegDecoderPlugin.hxx b/src/decoder/FfmpegDecoderPlugin.hxx
new file mode 100644
index 000000000..9a637fff0
--- /dev/null
+++ b/src/decoder/FfmpegDecoderPlugin.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_DECODER_FFMPEG_HXX
+#define MPD_DECODER_FFMPEG_HXX
+
+extern const struct decoder_plugin ffmpeg_decoder_plugin;
+
+#endif
diff --git a/src/decoder/FfmpegMetaData.cxx b/src/decoder/FfmpegMetaData.cxx
new file mode 100644
index 000000000..c383245d0
--- /dev/null
+++ b/src/decoder/FfmpegMetaData.cxx
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+
+/* necessary because libavutil/common.h uses UINT64_C */
+#define __STDC_CONSTANT_MACROS
+
+#include "config.h"
+#include "FfmpegMetaData.hxx"
+#include "TagTable.hxx"
+#include "TagHandler.hxx"
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "ffmpeg"
+
+static const struct tag_table ffmpeg_tags[] = {
+ { "year", TAG_DATE },
+ { "author-sort", TAG_ARTIST_SORT },
+ { "album_artist", TAG_ALBUM_ARTIST },
+ { "album_artist-sort", TAG_ALBUM_ARTIST_SORT },
+
+ /* sentinel */
+ { NULL, TAG_NUM_OF_ITEM_TYPES }
+};
+
+static void
+ffmpeg_copy_metadata(enum tag_type type,
+ AVDictionary *m, const char *name,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ AVDictionaryEntry *mt = NULL;
+
+ while ((mt = av_dict_get(m, name, mt, 0)) != NULL)
+ tag_handler_invoke_tag(handler, handler_ctx,
+ type, mt->value);
+}
+
+static void
+ffmpeg_scan_pairs(AVDictionary *dict,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ AVDictionaryEntry *i = NULL;
+
+ while ((i = av_dict_get(dict, "", i, AV_DICT_IGNORE_SUFFIX)) != NULL)
+ tag_handler_invoke_pair(handler, handler_ctx,
+ i->key, i->value);
+}
+
+void
+ffmpeg_scan_dictionary(AVDictionary *dict,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
+ ffmpeg_copy_metadata(tag_type(i), dict, tag_item_names[i],
+ handler, handler_ctx);
+
+ for (const struct tag_table *i = ffmpeg_tags;
+ i->name != NULL; ++i)
+ ffmpeg_copy_metadata(i->type, dict, i->name,
+ handler, handler_ctx);
+
+ if (handler->pair != NULL)
+ ffmpeg_scan_pairs(dict, handler, handler_ctx);
+}
diff --git a/src/decoder/FfmpegMetaData.hxx b/src/decoder/FfmpegMetaData.hxx
new file mode 100644
index 000000000..0fd73df04
--- /dev/null
+++ b/src/decoder/FfmpegMetaData.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_FFMPEG_METADATA_HXX
+#define MPD_FFMPEG_METADATA_HXX
+
+extern "C" {
+#include <libavformat/avformat.h>
+#include <libavutil/avutil.h>
+#include <libavutil/dict.h>
+}
+
+/* suppress the ffmpeg compatibility macro */
+#ifdef SampleFormat
+#undef SampleFormat
+#endif
+
+struct tag_handler;
+
+void
+ffmpeg_scan_dictionary(AVDictionary *dict,
+ const struct tag_handler *handler, void *handler_ctx);
+
+#endif
diff --git a/src/decoder/FlacCommon.cxx b/src/decoder/FlacCommon.cxx
new file mode 100644
index 000000000..5bcc20b97
--- /dev/null
+++ b/src/decoder/FlacCommon.cxx
@@ -0,0 +1,199 @@
+/*
+ * 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.
+ */
+
+/*
+ * Common data structures and functions used by FLAC and OggFLAC
+ */
+
+#include "config.h"
+#include "FlacCommon.hxx"
+#include "FlacMetadata.hxx"
+#include "FlacPcm.hxx"
+#include "CheckAudioFormat.hxx"
+
+#include <glib.h>
+
+#include <assert.h>
+
+flac_data::flac_data(struct decoder *_decoder,
+ struct input_stream *_input_stream)
+ :FlacInput(_input_stream, _decoder),
+ initialized(false), unsupported(false),
+ total_frames(0), first_frame(0), next_frame(0), position(0),
+ decoder(_decoder), input_stream(_input_stream)
+{
+}
+
+static SampleFormat
+flac_sample_format(unsigned bits_per_sample)
+{
+ switch (bits_per_sample) {
+ case 8:
+ return SampleFormat::S8;
+
+ case 16:
+ return SampleFormat::S16;
+
+ case 24:
+ return SampleFormat::S24_P32;
+
+ case 32:
+ return SampleFormat::S32;
+
+ default:
+ return SampleFormat::UNDEFINED;
+ }
+}
+
+static void
+flac_got_stream_info(struct flac_data *data,
+ const FLAC__StreamMetadata_StreamInfo *stream_info)
+{
+ if (data->initialized || data->unsupported)
+ return;
+
+ GError *error = nullptr;
+ if (!audio_format_init_checked(data->audio_format,
+ stream_info->sample_rate,
+ flac_sample_format(stream_info->bits_per_sample),
+ stream_info->channels, &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ data->unsupported = true;
+ return;
+ }
+
+ data->frame_size = data->audio_format.GetFrameSize();
+
+ if (data->total_frames == 0)
+ data->total_frames = stream_info->total_samples;
+
+ data->initialized = true;
+}
+
+void flac_metadata_common_cb(const FLAC__StreamMetadata * block,
+ struct flac_data *data)
+{
+ if (data->unsupported)
+ return;
+
+ struct replay_gain_info rgi;
+ char *mixramp_start;
+ char *mixramp_end;
+
+ switch (block->type) {
+ case FLAC__METADATA_TYPE_STREAMINFO:
+ flac_got_stream_info(data, &block->data.stream_info);
+ break;
+
+ case FLAC__METADATA_TYPE_VORBIS_COMMENT:
+ if (flac_parse_replay_gain(&rgi, block))
+ decoder_replay_gain(data->decoder, &rgi);
+
+ if (flac_parse_mixramp(&mixramp_start, &mixramp_end, block))
+ decoder_mixramp(data->decoder,
+ mixramp_start, mixramp_end);
+
+ flac_vorbis_comments_to_tag(data->tag,
+ &block->data.vorbis_comment);
+
+ default:
+ break;
+ }
+}
+
+/**
+ * This function attempts to call decoder_initialized() in case there
+ * was no STREAMINFO block. This is allowed for nonseekable streams,
+ * where the server sends us only a part of the file, without
+ * providing the STREAMINFO block from the beginning of the file
+ * (e.g. when seeking with SqueezeBox Server).
+ */
+static bool
+flac_got_first_frame(struct flac_data *data, const FLAC__FrameHeader *header)
+{
+ if (data->unsupported)
+ return false;
+
+ GError *error = nullptr;
+ if (!audio_format_init_checked(data->audio_format,
+ header->sample_rate,
+ flac_sample_format(header->bits_per_sample),
+ header->channels, &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ data->unsupported = true;
+ return false;
+ }
+
+ data->frame_size = data->audio_format.GetFrameSize();
+
+ decoder_initialized(data->decoder, data->audio_format,
+ data->input_stream->seekable,
+ (float)data->total_frames /
+ (float)data->audio_format.sample_rate);
+
+ data->initialized = true;
+
+ return true;
+}
+
+FLAC__StreamDecoderWriteStatus
+flac_common_write(struct flac_data *data, const FLAC__Frame * frame,
+ const FLAC__int32 *const buf[],
+ FLAC__uint64 nbytes)
+{
+ enum decoder_command cmd;
+ void *buffer;
+ unsigned bit_rate;
+
+ if (!data->initialized && !flac_got_first_frame(data, &frame->header))
+ return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
+
+ size_t buffer_size = frame->header.blocksize * data->frame_size;
+ buffer = data->buffer.Get(buffer_size);
+
+ flac_convert(buffer, frame->header.channels,
+ data->audio_format.format, buf,
+ 0, frame->header.blocksize);
+
+ if (nbytes > 0)
+ bit_rate = nbytes * 8 * frame->header.sample_rate /
+ (1000 * frame->header.blocksize);
+ else
+ bit_rate = 0;
+
+ cmd = decoder_data(data->decoder, data->input_stream,
+ buffer, buffer_size,
+ bit_rate);
+ data->next_frame += frame->header.blocksize;
+ switch (cmd) {
+ case DECODE_COMMAND_NONE:
+ case DECODE_COMMAND_START:
+ break;
+
+ case DECODE_COMMAND_STOP:
+ return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
+
+ case DECODE_COMMAND_SEEK:
+ return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
+ }
+
+ return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
+}
diff --git a/src/decoder/FlacCommon.hxx b/src/decoder/FlacCommon.hxx
new file mode 100644
index 000000000..f9fade6fc
--- /dev/null
+++ b/src/decoder/FlacCommon.hxx
@@ -0,0 +1,97 @@
+/*
+ * 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.
+ */
+
+/*
+ * Common data structures and functions used by FLAC and OggFLAC
+ */
+
+#ifndef MPD_FLAC_COMMON_HXX
+#define MPD_FLAC_COMMON_HXX
+
+#include "FlacInput.hxx"
+#include "DecoderAPI.hxx"
+#include "pcm/PcmBuffer.hxx"
+
+#include <FLAC/stream_decoder.h>
+#include <FLAC/metadata.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "flac"
+
+struct flac_data : public FlacInput {
+ PcmBuffer buffer;
+
+ /**
+ * The size of one frame in the output buffer.
+ */
+ unsigned frame_size;
+
+ /**
+ * Has decoder_initialized() been called yet?
+ */
+ bool initialized;
+
+ /**
+ * Does the FLAC file contain an unsupported audio format?
+ */
+ bool unsupported;
+
+ /**
+ * The validated audio format of the FLAC file. This
+ * attribute is defined if "initialized" is true.
+ */
+ AudioFormat audio_format;
+
+ /**
+ * The total number of frames in this song. The decoder
+ * plugin may initialize this attribute to override the value
+ * provided by libFLAC (e.g. for sub songs from a CUE sheet).
+ */
+ FLAC__uint64 total_frames;
+
+ /**
+ * The number of the first frame in this song. This is only
+ * non-zero if playing sub songs from a CUE sheet.
+ */
+ FLAC__uint64 first_frame;
+
+ /**
+ * The number of the next frame which is going to be decoded.
+ */
+ FLAC__uint64 next_frame;
+
+ FLAC__uint64 position;
+
+ struct decoder *decoder;
+ struct input_stream *input_stream;
+
+ Tag tag;
+
+ flac_data(struct decoder *decoder, struct input_stream *input_stream);
+};
+
+void flac_metadata_common_cb(const FLAC__StreamMetadata * block,
+ struct flac_data *data);
+
+FLAC__StreamDecoderWriteStatus
+flac_common_write(struct flac_data *data, const FLAC__Frame * frame,
+ const FLAC__int32 *const buf[],
+ FLAC__uint64 nbytes);
+
+#endif /* _FLAC_COMMON_H */
diff --git a/src/decoder/FlacDecoderPlugin.cxx b/src/decoder/FlacDecoderPlugin.cxx
new file mode 100644
index 000000000..d228c41b9
--- /dev/null
+++ b/src/decoder/FlacDecoderPlugin.cxx
@@ -0,0 +1,381 @@
+/*
+ * 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" /* must be first for large file support */
+#include "FlacDecoderPlugin.h"
+#include "FlacCommon.hxx"
+#include "FlacMetadata.hxx"
+#include "OggCodec.hxx"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <unistd.h>
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7
+#error libFLAC is too old
+#endif
+
+static void flacPrintErroredState(FLAC__StreamDecoderState state)
+{
+ switch (state) {
+ case FLAC__STREAM_DECODER_SEARCH_FOR_METADATA:
+ case FLAC__STREAM_DECODER_READ_METADATA:
+ case FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC:
+ case FLAC__STREAM_DECODER_READ_FRAME:
+ case FLAC__STREAM_DECODER_END_OF_STREAM:
+ return;
+
+ case FLAC__STREAM_DECODER_OGG_ERROR:
+ case FLAC__STREAM_DECODER_SEEK_ERROR:
+ case FLAC__STREAM_DECODER_ABORTED:
+ case FLAC__STREAM_DECODER_MEMORY_ALLOCATION_ERROR:
+ case FLAC__STREAM_DECODER_UNINITIALIZED:
+ break;
+ }
+
+ g_warning("%s\n", FLAC__StreamDecoderStateString[state]);
+}
+
+static void flacMetadata(G_GNUC_UNUSED const FLAC__StreamDecoder * dec,
+ const FLAC__StreamMetadata * block, void *vdata)
+{
+ flac_metadata_common_cb(block, (struct flac_data *) vdata);
+}
+
+static FLAC__StreamDecoderWriteStatus
+flac_write_cb(const FLAC__StreamDecoder *dec, const FLAC__Frame *frame,
+ const FLAC__int32 *const buf[], void *vdata)
+{
+ struct flac_data *data = (struct flac_data *) vdata;
+ FLAC__uint64 nbytes = 0;
+
+ if (FLAC__stream_decoder_get_decode_position(dec, &nbytes)) {
+ if (data->position > 0 && nbytes > data->position) {
+ nbytes -= data->position;
+ data->position += nbytes;
+ } else {
+ data->position = nbytes;
+ nbytes = 0;
+ }
+ } else
+ nbytes = 0;
+
+ return flac_common_write(data, frame, buf, nbytes);
+}
+
+static bool
+flac_scan_file(const char *file,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ FlacMetadataChain chain;
+ if (!chain.Read(file)) {
+ g_debug("Failed to read FLAC tags: %s",
+ chain.GetStatusString());
+ return false;
+ }
+
+ chain.Scan(handler, handler_ctx);
+ return true;
+}
+
+static bool
+flac_scan_stream(struct input_stream *is,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ FlacMetadataChain chain;
+ if (!chain.Read(is)) {
+ g_debug("Failed to read FLAC tags: %s",
+ chain.GetStatusString());
+ return false;
+ }
+
+ chain.Scan(handler, handler_ctx);
+ return true;
+}
+
+/**
+ * Some glue code around FLAC__stream_decoder_new().
+ */
+static FLAC__StreamDecoder *
+flac_decoder_new(void)
+{
+ FLAC__StreamDecoder *sd = FLAC__stream_decoder_new();
+ if (sd == nullptr) {
+ g_warning("FLAC__stream_decoder_new() failed");
+ return nullptr;
+ }
+
+ if(!FLAC__stream_decoder_set_metadata_respond(sd, FLAC__METADATA_TYPE_VORBIS_COMMENT))
+ g_debug("FLAC__stream_decoder_set_metadata_respond() has failed");
+
+ return sd;
+}
+
+static bool
+flac_decoder_initialize(struct flac_data *data, FLAC__StreamDecoder *sd,
+ FLAC__uint64 duration)
+{
+ data->total_frames = duration;
+
+ if (!FLAC__stream_decoder_process_until_end_of_metadata(sd)) {
+ g_warning("problem reading metadata");
+ return false;
+ }
+
+ if (data->initialized) {
+ /* done */
+ decoder_initialized(data->decoder, data->audio_format,
+ data->input_stream->seekable,
+ (float)data->total_frames /
+ (float)data->audio_format.sample_rate);
+ return true;
+ }
+
+ if (data->input_stream->seekable)
+ /* allow the workaround below only for nonseekable
+ streams*/
+ return false;
+
+ /* no stream_info packet found; try to initialize the decoder
+ from the first frame header */
+ FLAC__stream_decoder_process_single(sd);
+ return data->initialized;
+}
+
+static void
+flac_decoder_loop(struct flac_data *data, FLAC__StreamDecoder *flac_dec,
+ FLAC__uint64 t_start, FLAC__uint64 t_end)
+{
+ struct decoder *decoder = data->decoder;
+ enum decoder_command cmd;
+
+ data->first_frame = t_start;
+
+ while (true) {
+ if (!data->tag.IsEmpty()) {
+ cmd = decoder_tag(data->decoder, data->input_stream,
+ std::move(data->tag));
+ data->tag.Clear();
+ } else
+ cmd = decoder_get_command(decoder);
+
+ if (cmd == DECODE_COMMAND_SEEK) {
+ FLAC__uint64 seek_sample = t_start +
+ decoder_seek_where(decoder) *
+ data->audio_format.sample_rate;
+ if (seek_sample >= t_start &&
+ (t_end == 0 || seek_sample <= t_end) &&
+ FLAC__stream_decoder_seek_absolute(flac_dec, seek_sample)) {
+ data->next_frame = seek_sample;
+ data->position = 0;
+ decoder_command_finished(decoder);
+ } else
+ decoder_seek_error(decoder);
+ } else if (cmd == DECODE_COMMAND_STOP ||
+ FLAC__stream_decoder_get_state(flac_dec) == FLAC__STREAM_DECODER_END_OF_STREAM)
+ break;
+
+ if (t_end != 0 && data->next_frame >= t_end)
+ /* end of this sub track */
+ break;
+
+ if (!FLAC__stream_decoder_process_single(flac_dec) &&
+ decoder_get_command(decoder) == DECODE_COMMAND_NONE) {
+ /* a failure that was not triggered by a
+ decoder command */
+ flacPrintErroredState(FLAC__stream_decoder_get_state(flac_dec));
+ break;
+ }
+ }
+}
+
+static FLAC__StreamDecoderInitStatus
+stream_init_oggflac(FLAC__StreamDecoder *flac_dec, struct flac_data *data)
+{
+ return FLAC__stream_decoder_init_ogg_stream(flac_dec,
+ FlacInput::Read,
+ FlacInput::Seek,
+ FlacInput::Tell,
+ FlacInput::Length,
+ FlacInput::Eof,
+ flac_write_cb,
+ flacMetadata,
+ FlacInput::Error,
+ data);
+}
+
+static FLAC__StreamDecoderInitStatus
+stream_init_flac(FLAC__StreamDecoder *flac_dec, struct flac_data *data)
+{
+ return FLAC__stream_decoder_init_stream(flac_dec,
+ FlacInput::Read,
+ FlacInput::Seek,
+ FlacInput::Tell,
+ FlacInput::Length,
+ FlacInput::Eof,
+ flac_write_cb,
+ flacMetadata,
+ FlacInput::Error,
+ data);
+}
+
+static FLAC__StreamDecoderInitStatus
+stream_init(FLAC__StreamDecoder *flac_dec, struct flac_data *data, bool is_ogg)
+{
+ return is_ogg
+ ? stream_init_oggflac(flac_dec, data)
+ : stream_init_flac(flac_dec, data);
+}
+
+static void
+flac_decode_internal(struct decoder * decoder,
+ struct input_stream *input_stream,
+ bool is_ogg)
+{
+ FLAC__StreamDecoder *flac_dec;
+
+ flac_dec = flac_decoder_new();
+ if (flac_dec == nullptr)
+ return;
+
+ struct flac_data data(decoder, input_stream);
+
+ FLAC__StreamDecoderInitStatus status =
+ stream_init(flac_dec, &data, is_ogg);
+ if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) {
+ FLAC__stream_decoder_delete(flac_dec);
+ g_warning("%s", FLAC__StreamDecoderInitStatusString[status]);
+ return;
+ }
+
+ if (!flac_decoder_initialize(&data, flac_dec, 0)) {
+ FLAC__stream_decoder_finish(flac_dec);
+ FLAC__stream_decoder_delete(flac_dec);
+ return;
+ }
+
+ flac_decoder_loop(&data, flac_dec, 0, 0);
+
+ FLAC__stream_decoder_finish(flac_dec);
+ FLAC__stream_decoder_delete(flac_dec);
+}
+
+static void
+flac_decode(struct decoder * decoder, struct input_stream *input_stream)
+{
+ flac_decode_internal(decoder, input_stream, false);
+}
+
+static bool
+oggflac_init(gcc_unused const config_param &param)
+{
+ return !!FLAC_API_SUPPORTS_OGG_FLAC;
+}
+
+static bool
+oggflac_scan_file(const char *file,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ FlacMetadataChain chain;
+ if (!chain.ReadOgg(file)) {
+ g_debug("Failed to read OggFLAC tags: %s",
+ chain.GetStatusString());
+ return false;
+ }
+
+ chain.Scan(handler, handler_ctx);
+ return true;
+}
+
+static bool
+oggflac_scan_stream(struct input_stream *is,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ FlacMetadataChain chain;
+ if (!chain.ReadOgg(is)) {
+ g_debug("Failed to read OggFLAC tags: %s",
+ chain.GetStatusString());
+ return false;
+ }
+
+ chain.Scan(handler, handler_ctx);
+ return true;
+}
+
+static void
+oggflac_decode(struct decoder *decoder, struct input_stream *input_stream)
+{
+ if (ogg_codec_detect(decoder, input_stream) != OGG_CODEC_FLAC)
+ return;
+
+ /* rewind the stream, because ogg_codec_detect() has
+ moved it */
+ input_stream_lock_seek(input_stream, 0, SEEK_SET, nullptr);
+
+ flac_decode_internal(decoder, input_stream, true);
+}
+
+static const char *const oggflac_suffixes[] = { "ogg", "oga", nullptr };
+static const char *const oggflac_mime_types[] = {
+ "application/ogg",
+ "application/x-ogg",
+ "audio/ogg",
+ "audio/x-flac+ogg",
+ "audio/x-ogg",
+ nullptr
+};
+
+const struct decoder_plugin oggflac_decoder_plugin = {
+ "oggflac",
+ oggflac_init,
+ nullptr,
+ oggflac_decode,
+ nullptr,
+ oggflac_scan_file,
+ oggflac_scan_stream,
+ nullptr,
+ oggflac_suffixes,
+ oggflac_mime_types,
+};
+
+static const char *const flac_suffixes[] = { "flac", nullptr };
+static const char *const flac_mime_types[] = {
+ "application/flac",
+ "application/x-flac",
+ "audio/flac",
+ "audio/x-flac",
+ nullptr
+};
+
+const struct decoder_plugin flac_decoder_plugin = {
+ "flac",
+ nullptr,
+ nullptr,
+ flac_decode,
+ nullptr,
+ flac_scan_file,
+ flac_scan_stream,
+ nullptr,
+ flac_suffixes,
+ flac_mime_types,
+};
diff --git a/src/decoder/FlacDecoderPlugin.h b/src/decoder/FlacDecoderPlugin.h
new file mode 100644
index 000000000..c99deeef7
--- /dev/null
+++ b/src/decoder/FlacDecoderPlugin.h
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+
+#ifndef MPD_DECODER_FLAC_H
+#define MPD_DECODER_FLAC_H
+
+extern const struct decoder_plugin flac_decoder_plugin;
+extern const struct decoder_plugin oggflac_decoder_plugin;
+
+#endif
diff --git a/src/decoder/FlacIOHandle.cxx b/src/decoder/FlacIOHandle.cxx
new file mode 100644
index 000000000..16a07a9d1
--- /dev/null
+++ b/src/decoder/FlacIOHandle.cxx
@@ -0,0 +1,114 @@
+/*
+ * 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 "FlacIOHandle.hxx"
+#include "io_error.h"
+#include "gcc.h"
+
+#include <errno.h>
+
+static size_t
+FlacIORead(void *ptr, size_t size, size_t nmemb, FLAC__IOHandle handle)
+{
+ input_stream *is = (input_stream *)handle;
+
+ uint8_t *const p0 = (uint8_t *)ptr, *p = p0,
+ *const end = p0 + size * nmemb;
+
+ /* libFLAC is very picky about short reads, and expects the IO
+ callback to fill the whole buffer (undocumented!) */
+
+ GError *error = nullptr;
+ while (p < end) {
+ size_t nbytes = input_stream_lock_read(is, p, end - p, &error);
+ if (nbytes == 0) {
+ if (error == nullptr)
+ /* end of file */
+ break;
+
+ if (error->domain == errno_quark())
+ errno = error->code;
+ else
+ /* just some random non-zero
+ errno value */
+ errno = EINVAL;
+ g_error_free(error);
+ return 0;
+ }
+
+ p += nbytes;
+ }
+
+ /* libFLAC expects a clean errno after returning from the IO
+ callbacks (undocumented!) */
+ errno = 0;
+ return (p - p0) / size;
+}
+
+static int
+FlacIOSeek(FLAC__IOHandle handle, FLAC__int64 offset, int whence)
+{
+ input_stream *is = (input_stream *)handle;
+
+ return input_stream_lock_seek(is, offset, whence, nullptr) ? 0 : -1;
+}
+
+static FLAC__int64
+FlacIOTell(FLAC__IOHandle handle)
+{
+ input_stream *is = (input_stream *)handle;
+
+ return is->offset;
+}
+
+static int
+FlacIOEof(FLAC__IOHandle handle)
+{
+ input_stream *is = (input_stream *)handle;
+
+ return input_stream_lock_eof(is);
+}
+
+static int
+FlacIOClose(gcc_unused FLAC__IOHandle handle)
+{
+ /* no-op because the libFLAC caller is repsonsible for closing
+ the #input_stream */
+
+ return 0;
+}
+
+const FLAC__IOCallbacks flac_io_callbacks = {
+ FlacIORead,
+ nullptr,
+ nullptr,
+ nullptr,
+ FlacIOEof,
+ FlacIOClose,
+};
+
+const FLAC__IOCallbacks flac_io_callbacks_seekable = {
+ FlacIORead,
+ nullptr,
+ FlacIOSeek,
+ FlacIOTell,
+ FlacIOEof,
+ FlacIOClose,
+};
diff --git a/src/decoder/FlacIOHandle.hxx b/src/decoder/FlacIOHandle.hxx
new file mode 100644
index 000000000..3216dafa4
--- /dev/null
+++ b/src/decoder/FlacIOHandle.hxx
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+#ifndef MPD_FLAC_IO_HANDLE_HXX
+#define MPD_FLAC_IO_HANDLE_HXX
+
+#include "gcc.h"
+#include "InputStream.hxx"
+
+#include <FLAC/callback.h>
+
+extern const FLAC__IOCallbacks flac_io_callbacks;
+extern const FLAC__IOCallbacks flac_io_callbacks_seekable;
+
+static inline FLAC__IOHandle
+ToFlacIOHandle(input_stream *is)
+{
+ return (FLAC__IOHandle)is;
+}
+
+static inline const FLAC__IOCallbacks &
+GetFlacIOCallbacks(const input_stream *is)
+{
+ return is->seekable
+ ? flac_io_callbacks_seekable
+ : flac_io_callbacks;
+}
+
+#endif
diff --git a/src/decoder/FlacInput.cxx b/src/decoder/FlacInput.cxx
new file mode 100644
index 000000000..0bb5ec7d7
--- /dev/null
+++ b/src/decoder/FlacInput.cxx
@@ -0,0 +1,149 @@
+/*
+ * 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 "FlacInput.hxx"
+#include "DecoderAPI.hxx"
+#include "gcc.h"
+#include "InputStream.hxx"
+
+FLAC__StreamDecoderReadStatus
+FlacInput::Read(FLAC__byte buffer[], size_t *bytes)
+{
+ size_t r = decoder_read(decoder, input_stream, (void *)buffer, *bytes);
+ *bytes = r;
+
+ if (r == 0) {
+ if (input_stream_lock_eof(input_stream) ||
+ (decoder != nullptr &&
+ decoder_get_command(decoder) != DECODE_COMMAND_NONE))
+ return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM;
+ else
+ return FLAC__STREAM_DECODER_READ_STATUS_ABORT;
+ }
+
+ return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
+}
+
+FLAC__StreamDecoderSeekStatus
+FlacInput::Seek(FLAC__uint64 absolute_byte_offset)
+{
+ if (!input_stream->seekable)
+ return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED;
+
+ if (!input_stream_lock_seek(input_stream,
+ absolute_byte_offset, SEEK_SET,
+ nullptr))
+ return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR;
+
+ return FLAC__STREAM_DECODER_SEEK_STATUS_OK;
+}
+
+FLAC__StreamDecoderTellStatus
+FlacInput::Tell(FLAC__uint64 *absolute_byte_offset)
+{
+ if (!input_stream->seekable)
+ return FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED;
+
+ *absolute_byte_offset = (FLAC__uint64)input_stream->offset;
+ return FLAC__STREAM_DECODER_TELL_STATUS_OK;
+}
+
+FLAC__StreamDecoderLengthStatus
+FlacInput::Length(FLAC__uint64 *stream_length)
+{
+ if (input_stream->size < 0)
+ return FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED;
+
+ *stream_length = (FLAC__uint64)input_stream->size;
+ return FLAC__STREAM_DECODER_LENGTH_STATUS_OK;
+}
+
+FLAC__bool
+FlacInput::Eof()
+{
+ return (decoder != nullptr &&
+ decoder_get_command(decoder) != DECODE_COMMAND_NONE &&
+ decoder_get_command(decoder) != DECODE_COMMAND_SEEK) ||
+ input_stream_lock_eof(input_stream);
+}
+
+void
+FlacInput::Error(FLAC__StreamDecoderErrorStatus status)
+{
+ if (decoder == nullptr ||
+ decoder_get_command(decoder) != DECODE_COMMAND_STOP)
+ g_warning("%s", FLAC__StreamDecoderErrorStatusString[status]);
+}
+
+FLAC__StreamDecoderReadStatus
+FlacInput::Read(gcc_unused const FLAC__StreamDecoder *flac_decoder,
+ FLAC__byte buffer[], size_t *bytes,
+ void *client_data)
+{
+ FlacInput *i = (FlacInput *)client_data;
+
+ return i->Read(buffer, bytes);
+}
+
+FLAC__StreamDecoderSeekStatus
+FlacInput::Seek(gcc_unused const FLAC__StreamDecoder *flac_decoder,
+ FLAC__uint64 absolute_byte_offset, void *client_data)
+{
+ FlacInput *i = (FlacInput *)client_data;
+
+ return i->Seek(absolute_byte_offset);
+}
+
+FLAC__StreamDecoderTellStatus
+FlacInput::Tell(gcc_unused const FLAC__StreamDecoder *flac_decoder,
+ FLAC__uint64 *absolute_byte_offset, void *client_data)
+{
+ FlacInput *i = (FlacInput *)client_data;
+
+ return i->Tell(absolute_byte_offset);
+}
+
+FLAC__StreamDecoderLengthStatus
+FlacInput::Length(gcc_unused const FLAC__StreamDecoder *flac_decoder,
+ FLAC__uint64 *stream_length, void *client_data)
+{
+ FlacInput *i = (FlacInput *)client_data;
+
+ return i->Length(stream_length);
+}
+
+FLAC__bool
+FlacInput::Eof(gcc_unused const FLAC__StreamDecoder *flac_decoder,
+ void *client_data)
+{
+ FlacInput *i = (FlacInput *)client_data;
+
+ return i->Eof();
+}
+
+void
+FlacInput::Error(gcc_unused const FLAC__StreamDecoder *decoder,
+ FLAC__StreamDecoderErrorStatus status, void *client_data)
+{
+ FlacInput *i = (FlacInput *)client_data;
+
+ i->Error(status);
+}
+
diff --git a/src/decoder/FlacInput.hxx b/src/decoder/FlacInput.hxx
new file mode 100644
index 000000000..8fc69f960
--- /dev/null
+++ b/src/decoder/FlacInput.hxx
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+
+#ifndef MPD_FLAC_INPUT_HXX
+#define MPD_FLAC_INPUT_HXX
+
+#include <FLAC/stream_decoder.h>
+
+/**
+ * This class wraps an #input_stream in libFLAC stream decoder
+ * callbacks.
+ */
+class FlacInput {
+ struct decoder *decoder;
+
+ struct input_stream *input_stream;
+
+public:
+ FlacInput(struct input_stream *_input_stream,
+ struct decoder *_decoder=nullptr)
+ :decoder(_decoder), input_stream(_input_stream) {}
+
+protected:
+ FLAC__StreamDecoderReadStatus Read(FLAC__byte buffer[], size_t *bytes);
+ FLAC__StreamDecoderSeekStatus Seek(FLAC__uint64 absolute_byte_offset);
+ FLAC__StreamDecoderTellStatus Tell(FLAC__uint64 *absolute_byte_offset);
+ FLAC__StreamDecoderLengthStatus Length(FLAC__uint64 *stream_length);
+ FLAC__bool Eof();
+ void Error(FLAC__StreamDecoderErrorStatus status);
+
+public:
+ static FLAC__StreamDecoderReadStatus
+ Read(const FLAC__StreamDecoder *flac_decoder,
+ FLAC__byte buffer[], size_t *bytes, void *client_data);
+
+ static FLAC__StreamDecoderSeekStatus
+ Seek(const FLAC__StreamDecoder *flac_decoder,
+ FLAC__uint64 absolute_byte_offset, void *client_data);
+
+ static FLAC__StreamDecoderTellStatus
+ Tell(const FLAC__StreamDecoder *flac_decoder,
+ FLAC__uint64 *absolute_byte_offset, void *client_data);
+
+ static FLAC__StreamDecoderLengthStatus
+ Length(const FLAC__StreamDecoder *flac_decoder,
+ FLAC__uint64 *stream_length, void *client_data);
+
+ static FLAC__bool
+ Eof(const FLAC__StreamDecoder *flac_decoder, void *client_data);
+
+ static void
+ Error(const FLAC__StreamDecoder *decoder,
+ FLAC__StreamDecoderErrorStatus status, void *client_data);
+};
+
+#endif
diff --git a/src/decoder/FlacMetadata.cxx b/src/decoder/FlacMetadata.cxx
new file mode 100644
index 000000000..be15092b5
--- /dev/null
+++ b/src/decoder/FlacMetadata.cxx
@@ -0,0 +1,249 @@
+/*
+ * 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 "FlacMetadata.hxx"
+#include "XiphTags.hxx"
+#include "Tag.hxx"
+#include "TagHandler.hxx"
+#include "TagTable.hxx"
+#include "replay_gain_info.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <stdlib.h>
+
+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)
+ 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;
+
+ return true;
+}
+
+bool
+flac_parse_replay_gain(struct replay_gain_info *rgi,
+ const FLAC__StreamMetadata *block)
+{
+ bool found = false;
+
+ replay_gain_info_init(rgi);
+
+ if (flac_find_float_comment(block, "replaygain_album_gain",
+ &rgi->tuples[REPLAY_GAIN_ALBUM].gain))
+ found = true;
+ if (flac_find_float_comment(block, "replaygain_album_peak",
+ &rgi->tuples[REPLAY_GAIN_ALBUM].peak))
+ found = true;
+ if (flac_find_float_comment(block, "replaygain_track_gain",
+ &rgi->tuples[REPLAY_GAIN_TRACK].gain))
+ found = true;
+ if (flac_find_float_comment(block, "replaygain_track_peak",
+ &rgi->tuples[REPLAY_GAIN_TRACK].peak))
+ found = true;
+
+ return found;
+}
+
+static bool
+flac_find_string_comment(const FLAC__StreamMetadata *block,
+ const char *cmnt, char **str)
+{
+ int offset;
+ size_t pos;
+ int len;
+ const unsigned char *p;
+
+ *str = nullptr;
+ 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)
+ return false;
+
+ p = &block->data.vorbis_comment.comments[offset].entry[pos];
+ *str = g_strndup((const char *)p, len);
+
+ return true;
+}
+
+bool
+flac_parse_mixramp(char **mixramp_start, char **mixramp_end,
+ const FLAC__StreamMetadata *block)
+{
+ bool found = false;
+
+ if (flac_find_string_comment(block, "mixramp_start", mixramp_start))
+ found = true;
+ if (flac_find_string_comment(block, "mixramp_end", mixramp_end))
+ found = true;
+
+ return found;
+}
+
+/**
+ * Checks if the specified name matches the entry's name, and if yes,
+ * returns the comment value (not null-temrinated).
+ */
+static const char *
+flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry,
+ const char *name, size_t *length_r)
+{
+ size_t name_length = strlen(name);
+ const char *comment = (const char*)entry->entry;
+
+ if (entry->length <= name_length ||
+ g_ascii_strncasecmp(comment, name, name_length) != 0)
+ return nullptr;
+
+ if (comment[name_length] == '=') {
+ *length_r = entry->length - name_length - 1;
+ return comment + name_length + 1;
+ }
+
+ return nullptr;
+}
+
+/**
+ * Check if the comment's name equals the passed name, and if so, copy
+ * the comment value into the tag.
+ */
+static bool
+flac_copy_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry,
+ const char *name, enum tag_type 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);
+ if (value != nullptr) {
+ char *p = g_strndup(value, value_length);
+ tag_handler_invoke_tag(handler, handler_ctx, tag_type, p);
+ g_free(p);
+ return true;
+ }
+
+ return false;
+}
+
+static void
+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;
+ tag_handler_invoke_pair(handler, handler_ctx,
+ name, value);
+ }
+
+ g_free(name);
+ }
+
+ for (const struct tag_table *i = xiph_tags; i->name != nullptr; ++i)
+ if (flac_copy_comment(entry, i->name, i->type,
+ handler, handler_ctx))
+ return;
+
+ for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
+ if (flac_copy_comment(entry,
+ tag_item_names[i], (enum tag_type)i,
+ handler, handler_ctx))
+ return;
+}
+
+static void
+flac_scan_comments(const FLAC__StreamMetadata_VorbisComment *comment,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ for (unsigned i = 0; i < comment->num_comments; ++i)
+ flac_scan_comment(&comment->comments[i],
+ handler, handler_ctx);
+}
+
+void
+flac_scan_metadata(const FLAC__StreamMetadata *block,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ switch (block->type) {
+ case FLAC__METADATA_TYPE_VORBIS_COMMENT:
+ flac_scan_comments(&block->data.vorbis_comment,
+ handler, handler_ctx);
+ break;
+
+ case FLAC__METADATA_TYPE_STREAMINFO:
+ if (block->data.stream_info.sample_rate > 0)
+ tag_handler_invoke_duration(handler, handler_ctx,
+ flac_duration(&block->data.stream_info));
+ break;
+
+ default:
+ break;
+ }
+}
+
+void
+flac_vorbis_comments_to_tag(Tag &tag,
+ const FLAC__StreamMetadata_VorbisComment *comment)
+{
+ flac_scan_comments(comment, &add_tag_handler, &tag);
+}
+
+void
+FlacMetadataChain::Scan(const struct tag_handler *handler, void *handler_ctx)
+{
+ FLACMetadataIterator iterator(*this);
+
+ do {
+ FLAC__StreamMetadata *block = iterator.GetBlock();
+ if (block == nullptr)
+ break;
+
+ flac_scan_metadata(block, handler, handler_ctx);
+ } while (iterator.Next());
+}
diff --git a/src/decoder/FlacMetadata.hxx b/src/decoder/FlacMetadata.hxx
new file mode 100644
index 000000000..57769672f
--- /dev/null
+++ b/src/decoder/FlacMetadata.hxx
@@ -0,0 +1,140 @@
+/*
+ * 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.
+ */
+
+#ifndef MPD_FLAC_METADATA_H
+#define MPD_FLAC_METADATA_H
+
+#include "gcc.h"
+#include "FlacIOHandle.hxx"
+
+#include <FLAC/metadata.h>
+
+#include <assert.h>
+
+class FlacMetadataChain {
+ FLAC__Metadata_Chain *chain;
+
+public:
+ FlacMetadataChain():chain(::FLAC__metadata_chain_new()) {}
+
+ ~FlacMetadataChain() {
+ ::FLAC__metadata_chain_delete(chain);
+ }
+
+ explicit operator FLAC__Metadata_Chain *() {
+ return chain;
+ }
+
+ bool Read(const char *path) {
+ return ::FLAC__metadata_chain_read(chain, path);
+ }
+
+ bool Read(FLAC__IOHandle handle, FLAC__IOCallbacks callbacks) {
+ return ::FLAC__metadata_chain_read_with_callbacks(chain,
+ handle,
+ callbacks);
+ }
+
+ bool Read(input_stream *is) {
+ return Read(::ToFlacIOHandle(is), ::GetFlacIOCallbacks(is));
+ }
+
+ bool ReadOgg(const char *path) {
+ return ::FLAC__metadata_chain_read_ogg(chain, path);
+ }
+
+ bool ReadOgg(FLAC__IOHandle handle, FLAC__IOCallbacks callbacks) {
+ return ::FLAC__metadata_chain_read_ogg_with_callbacks(chain,
+ handle,
+ callbacks);
+ }
+
+ bool ReadOgg(input_stream *is) {
+ return ReadOgg(::ToFlacIOHandle(is), ::GetFlacIOCallbacks(is));
+ }
+
+ gcc_pure
+ FLAC__Metadata_ChainStatus GetStatus() const {
+ return ::FLAC__metadata_chain_status(chain);
+ }
+
+ gcc_pure
+ const char *GetStatusString() const {
+ return FLAC__Metadata_ChainStatusString[GetStatus()];
+ }
+
+ void Scan(const struct tag_handler *handler, void *handler_ctx);
+};
+
+class FLACMetadataIterator {
+ FLAC__Metadata_Iterator *iterator;
+
+public:
+ FLACMetadataIterator():iterator(::FLAC__metadata_iterator_new()) {}
+
+ FLACMetadataIterator(FlacMetadataChain &chain)
+ :iterator(::FLAC__metadata_iterator_new()) {
+ ::FLAC__metadata_iterator_init(iterator,
+ (FLAC__Metadata_Chain *)chain);
+ }
+
+ ~FLACMetadataIterator() {
+ ::FLAC__metadata_iterator_delete(iterator);
+ }
+
+ bool Next() {
+ return ::FLAC__metadata_iterator_next(iterator);
+ }
+
+ gcc_pure
+ FLAC__StreamMetadata *GetBlock() {
+ return ::FLAC__metadata_iterator_get_block(iterator);
+ }
+};
+
+struct tag_handler;
+struct Tag;
+struct replay_gain_info;
+
+static inline unsigned
+flac_duration(const FLAC__StreamMetadata_StreamInfo *stream_info)
+{
+ assert(stream_info->sample_rate > 0);
+
+ return (stream_info->total_samples + stream_info->sample_rate - 1) /
+ stream_info->sample_rate;
+}
+
+bool
+flac_parse_replay_gain(struct replay_gain_info *rgi,
+ const FLAC__StreamMetadata *block);
+
+bool
+flac_parse_mixramp(char **mixramp_start, char **mixramp_end,
+ const FLAC__StreamMetadata *block);
+
+void
+flac_vorbis_comments_to_tag(Tag &tag,
+ const FLAC__StreamMetadata_VorbisComment *comment);
+
+void
+flac_scan_metadata(const FLAC__StreamMetadata *block,
+ const struct tag_handler *handler, void *handler_ctx);
+
+#endif
diff --git a/src/decoder/FlacPcm.cxx b/src/decoder/FlacPcm.cxx
new file mode 100644
index 000000000..ff855fa70
--- /dev/null
+++ b/src/decoder/FlacPcm.cxx
@@ -0,0 +1,110 @@
+/*
+ * 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 "FlacPcm.hxx"
+
+#include <assert.h>
+
+static void flac_convert_stereo16(int16_t *dest,
+ const FLAC__int32 * const buf[],
+ unsigned int position, unsigned int end)
+{
+ for (; position < end; ++position) {
+ *dest++ = buf[0][position];
+ *dest++ = buf[1][position];
+ }
+}
+
+static void
+flac_convert_16(int16_t *dest,
+ unsigned int num_channels,
+ const FLAC__int32 * const buf[],
+ unsigned int position, unsigned int end)
+{
+ unsigned int c_chan;
+
+ for (; position < end; ++position)
+ for (c_chan = 0; c_chan < num_channels; c_chan++)
+ *dest++ = buf[c_chan][position];
+}
+
+/**
+ * Note: this function also handles 24 bit files!
+ */
+static void
+flac_convert_32(int32_t *dest,
+ unsigned int num_channels,
+ const FLAC__int32 * const buf[],
+ unsigned int position, unsigned int end)
+{
+ unsigned int c_chan;
+
+ for (; position < end; ++position)
+ for (c_chan = 0; c_chan < num_channels; c_chan++)
+ *dest++ = buf[c_chan][position];
+}
+
+static void
+flac_convert_8(int8_t *dest,
+ unsigned int num_channels,
+ const FLAC__int32 * const buf[],
+ unsigned int position, unsigned int end)
+{
+ unsigned int c_chan;
+
+ for (; position < end; ++position)
+ for (c_chan = 0; c_chan < num_channels; c_chan++)
+ *dest++ = buf[c_chan][position];
+}
+
+void
+flac_convert(void *dest,
+ unsigned int num_channels, SampleFormat sample_format,
+ const FLAC__int32 *const buf[],
+ unsigned int position, unsigned int end)
+{
+ switch (sample_format) {
+ case SampleFormat::S16:
+ if (num_channels == 2)
+ flac_convert_stereo16((int16_t*)dest, buf,
+ position, end);
+ else
+ flac_convert_16((int16_t*)dest, num_channels, buf,
+ position, end);
+ break;
+
+ case SampleFormat::S24_P32:
+ case SampleFormat::S32:
+ flac_convert_32((int32_t*)dest, num_channels, buf,
+ position, end);
+ break;
+
+ case SampleFormat::S8:
+ flac_convert_8((int8_t*)dest, num_channels, buf,
+ position, end);
+ break;
+
+ case SampleFormat::FLOAT:
+ case SampleFormat::DSD:
+ case SampleFormat::UNDEFINED:
+ assert(false);
+ gcc_unreachable();
+ }
+}
diff --git a/src/decoder/FlacPcm.hxx b/src/decoder/FlacPcm.hxx
new file mode 100644
index 000000000..fa85f65dd
--- /dev/null
+++ b/src/decoder/FlacPcm.hxx
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+#ifndef MPD_FLAC_PCM_HXX
+#define MPD_FLAC_PCM_HXX
+
+#include "AudioFormat.hxx"
+
+#include <FLAC/ordinals.h>
+
+void
+flac_convert(void *dest,
+ unsigned int num_channels, SampleFormat sample_format,
+ const FLAC__int32 *const buf[],
+ unsigned int position, unsigned int end);
+
+#endif
diff --git a/src/decoder/FluidsynthDecoderPlugin.cxx b/src/decoder/FluidsynthDecoderPlugin.cxx
new file mode 100644
index 000000000..7d56d4f1f
--- /dev/null
+++ b/src/decoder/FluidsynthDecoderPlugin.cxx
@@ -0,0 +1,224 @@
+/*
+ * 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 "FluidsynthDecoderPlugin.hxx"
+#include "DecoderAPI.hxx"
+#include "CheckAudioFormat.hxx"
+#include "conf.h"
+
+#include <glib.h>
+
+#include <fluidsynth.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "fluidsynth"
+
+static unsigned sample_rate;
+static const char *soundfont_path;
+
+/**
+ * Convert a fluidsynth log level to a GLib log level.
+ */
+static GLogLevelFlags
+fluidsynth_level_to_glib(enum fluid_log_level level)
+{
+ switch (level) {
+ case FLUID_PANIC:
+ case FLUID_ERR:
+ return G_LOG_LEVEL_CRITICAL;
+
+ case FLUID_WARN:
+ return G_LOG_LEVEL_WARNING;
+
+ case FLUID_INFO:
+ return G_LOG_LEVEL_INFO;
+
+ case FLUID_DBG:
+ case LAST_LOG_LEVEL:
+ return G_LOG_LEVEL_DEBUG;
+ }
+
+ /* invalid fluidsynth log level */
+ return G_LOG_LEVEL_MESSAGE;
+}
+
+/**
+ * The fluidsynth logging callback. It forwards messages to the GLib
+ * logging library.
+ */
+static void
+fluidsynth_mpd_log_function(int level, char *message, G_GNUC_UNUSED void *data)
+{
+ g_log(G_LOG_DOMAIN, fluidsynth_level_to_glib(fluid_log_level(level)),
+ "%s", message);
+}
+
+static bool
+fluidsynth_init(const config_param &param)
+{
+ GError *error = nullptr;
+
+ sample_rate = param.GetBlockValue("sample_rate", 48000u);
+ if (!audio_check_sample_rate(sample_rate, &error)) {
+ g_warning("%s\n", error->message);
+ g_error_free(error);
+ return false;
+ }
+
+ soundfont_path = param.GetBlockValue("soundfont",
+ "/usr/share/sounds/sf2/FluidR3_GM.sf2");
+
+ fluid_set_log_function(LAST_LOG_LEVEL,
+ fluidsynth_mpd_log_function, nullptr);
+
+ return true;
+}
+
+static void
+fluidsynth_file_decode(struct decoder *decoder, const char *path_fs)
+{
+ char setting_sample_rate[] = "synth.sample-rate";
+ /*
+ char setting_verbose[] = "synth.verbose";
+ char setting_yes[] = "yes";
+ */
+ fluid_settings_t *settings;
+ fluid_synth_t *synth;
+ fluid_player_t *player;
+ int ret;
+ enum decoder_command cmd;
+
+ /* set up fluid settings */
+
+ settings = new_fluid_settings();
+ if (settings == nullptr)
+ return;
+
+ fluid_settings_setnum(settings, setting_sample_rate, sample_rate);
+
+ /*
+ fluid_settings_setstr(settings, setting_verbose, setting_yes);
+ */
+
+ /* create the fluid synth */
+
+ synth = new_fluid_synth(settings);
+ if (synth == nullptr) {
+ delete_fluid_settings(settings);
+ return;
+ }
+
+ ret = fluid_synth_sfload(synth, soundfont_path, true);
+ if (ret < 0) {
+ g_warning("fluid_synth_sfload() failed");
+ delete_fluid_synth(synth);
+ delete_fluid_settings(settings);
+ return;
+ }
+
+ /* create the fluid player */
+
+ player = new_fluid_player(synth);
+ if (player == nullptr) {
+ delete_fluid_synth(synth);
+ delete_fluid_settings(settings);
+ return;
+ }
+
+ ret = fluid_player_add(player, path_fs);
+ if (ret != 0) {
+ g_warning("fluid_player_add() failed");
+ delete_fluid_player(player);
+ delete_fluid_synth(synth);
+ delete_fluid_settings(settings);
+ return;
+ }
+
+ /* start the player */
+
+ ret = fluid_player_play(player);
+ if (ret != 0) {
+ g_warning("fluid_player_play() failed");
+ delete_fluid_player(player);
+ delete_fluid_synth(synth);
+ delete_fluid_settings(settings);
+ return;
+ }
+
+ /* initialization complete - announce the audio format to the
+ MPD core */
+
+ const AudioFormat audio_format(sample_rate, SampleFormat::S16, 2);
+ decoder_initialized(decoder, audio_format, false, -1);
+
+ while (fluid_player_get_status(player) == FLUID_PLAYER_PLAYING) {
+ int16_t buffer[2048];
+ const unsigned max_frames = G_N_ELEMENTS(buffer) / 2;
+
+ /* read samples from fluidsynth and send them to the
+ MPD core */
+
+ ret = fluid_synth_write_s16(synth, max_frames,
+ buffer, 0, 2,
+ buffer, 1, 2);
+ if (ret != 0)
+ break;
+
+ cmd = decoder_data(decoder, nullptr, buffer, sizeof(buffer),
+ 0);
+ if (cmd != DECODE_COMMAND_NONE)
+ break;
+ }
+
+ /* clean up */
+
+ fluid_player_stop(player);
+ fluid_player_join(player);
+
+ delete_fluid_player(player);
+ delete_fluid_synth(synth);
+ delete_fluid_settings(settings);
+}
+
+static bool
+fluidsynth_scan_file(const char *file,
+ G_GNUC_UNUSED const struct tag_handler *handler,
+ G_GNUC_UNUSED void *handler_ctx)
+{
+ return fluid_is_midifile(file);
+}
+
+static const char *const fluidsynth_suffixes[] = {
+ "mid",
+ nullptr
+};
+
+const struct decoder_plugin fluidsynth_decoder_plugin = {
+ "fluidsynth",
+ fluidsynth_init,
+ nullptr,
+ nullptr,
+ fluidsynth_file_decode,
+ fluidsynth_scan_file,
+ nullptr,
+ nullptr,
+ fluidsynth_suffixes,
+ nullptr,
+};
diff --git a/src/decoder/FluidsynthDecoderPlugin.hxx b/src/decoder/FluidsynthDecoderPlugin.hxx
new file mode 100644
index 000000000..40ed7e4d8
--- /dev/null
+++ b/src/decoder/FluidsynthDecoderPlugin.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_DECODER_FLUIDSYNTH_HXX
+#define MPD_DECODER_FLUIDSYNTH_HXX
+
+extern const struct decoder_plugin fluidsynth_decoder_plugin;
+
+#endif
diff --git a/src/decoder/GmeDecoderPlugin.cxx b/src/decoder/GmeDecoderPlugin.cxx
new file mode 100644
index 000000000..d8edbe4cb
--- /dev/null
+++ b/src/decoder/GmeDecoderPlugin.cxx
@@ -0,0 +1,289 @@
+/*
+ * 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 "GmeDecoderPlugin.hxx"
+#include "DecoderAPI.hxx"
+#include "CheckAudioFormat.hxx"
+#include "TagHandler.hxx"
+#include "util/UriUtil.hxx"
+
+#include <glib.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <gme/gme.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "gme"
+
+#define SUBTUNE_PREFIX "tune_"
+
+static constexpr unsigned GME_SAMPLE_RATE = 44100;
+static constexpr unsigned GME_CHANNELS = 2;
+static constexpr unsigned GME_BUFFER_FRAMES = 2048;
+static constexpr unsigned GME_BUFFER_SAMPLES =
+ GME_BUFFER_FRAMES * GME_CHANNELS;
+
+/**
+ * returns the file path stripped of any /tune_xxx.* subtune
+ * suffix
+ */
+static char *
+get_container_name(const char *path_fs)
+{
+ const char *subtune_suffix = uri_get_suffix(path_fs);
+ char *path_container = g_strdup(path_fs);
+ char *pat = g_strconcat("*/" SUBTUNE_PREFIX "???.",
+ subtune_suffix, nullptr);
+ GPatternSpec *path_with_subtune = g_pattern_spec_new(pat);
+ g_free(pat);
+ if (!g_pattern_match(path_with_subtune,
+ strlen(path_container), path_container, nullptr)) {
+ g_pattern_spec_free(path_with_subtune);
+ return path_container;
+ }
+
+ char *ptr = g_strrstr(path_container, "/" SUBTUNE_PREFIX);
+ if (ptr != nullptr)
+ *ptr='\0';
+
+ g_pattern_spec_free(path_with_subtune);
+ return path_container;
+}
+
+/**
+ * returns tune number from file.nsf/tune_xxx.* style path or 0 if no subtune
+ * is appended.
+ */
+static int
+get_song_num(const char *path_fs)
+{
+ const char *subtune_suffix = uri_get_suffix(path_fs);
+ char *pat = g_strconcat("*/" SUBTUNE_PREFIX "???.",
+ subtune_suffix, nullptr);
+ GPatternSpec *path_with_subtune = g_pattern_spec_new(pat);
+ g_free(pat);
+
+ if (g_pattern_match(path_with_subtune,
+ strlen(path_fs), path_fs, nullptr)) {
+ char *sub = g_strrstr(path_fs, "/" SUBTUNE_PREFIX);
+ g_pattern_spec_free(path_with_subtune);
+ if (!sub)
+ return 0;
+
+ sub += strlen("/" SUBTUNE_PREFIX);
+ int song_num = strtol(sub, nullptr, 10);
+
+ return song_num - 1;
+ } else {
+ g_pattern_spec_free(path_with_subtune);
+ return 0;
+ }
+}
+
+static char *
+gme_container_scan(const char *path_fs, const unsigned int tnum)
+{
+ Music_Emu *emu;
+ const char *gme_err = gme_open_file(path_fs, &emu, GME_SAMPLE_RATE);
+ if (gme_err != nullptr) {
+ g_warning("%s", gme_err);
+ return nullptr;
+ }
+
+ const unsigned num_songs = gme_track_count(emu);
+ /* if it only contains a single tune, don't treat as container */
+ if (num_songs < 2)
+ return nullptr;
+
+ const char *subtune_suffix = uri_get_suffix(path_fs);
+ if (tnum <= num_songs){
+ char *subtune = g_strdup_printf(
+ SUBTUNE_PREFIX "%03u.%s", tnum, subtune_suffix);
+ return subtune;
+ } else
+ return nullptr;
+}
+
+static void
+gme_file_decode(struct decoder *decoder, const char *path_fs)
+{
+ char *path_container = get_container_name(path_fs);
+
+ Music_Emu *emu;
+ const char *gme_err =
+ gme_open_file(path_container, &emu, GME_SAMPLE_RATE);
+ g_free(path_container);
+ if (gme_err != nullptr) {
+ g_warning("%s", gme_err);
+ return;
+ }
+
+ gme_info_t *ti;
+ const int song_num = get_song_num(path_fs);
+ gme_err = gme_track_info(emu, &ti, song_num);
+ if (gme_err != nullptr) {
+ g_warning("%s", gme_err);
+ gme_delete(emu);
+ return;
+ }
+
+ const float song_len = ti->length > 0
+ ? ti->length / 1000.0
+ : -1.0;
+
+ /* initialize the MPD decoder */
+
+ GError *error = nullptr;
+ AudioFormat audio_format;
+ if (!audio_format_init_checked(audio_format, GME_SAMPLE_RATE,
+ SampleFormat::S16, GME_CHANNELS,
+ &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ gme_free_info(ti);
+ gme_delete(emu);
+ return;
+ }
+
+ decoder_initialized(decoder, audio_format, true, song_len);
+
+ gme_err = gme_start_track(emu, song_num);
+ if (gme_err != nullptr)
+ g_warning("%s", gme_err);
+
+ if (ti->length > 0)
+ gme_set_fade(emu, ti->length);
+
+ /* play */
+ enum decoder_command cmd;
+ do {
+ short buf[GME_BUFFER_SAMPLES];
+ gme_err = gme_play(emu, GME_BUFFER_SAMPLES, buf);
+ if (gme_err != nullptr) {
+ g_warning("%s", gme_err);
+ return;
+ }
+
+ cmd = decoder_data(decoder, nullptr, buf, sizeof(buf), 0);
+ if (cmd == DECODE_COMMAND_SEEK) {
+ float where = decoder_seek_where(decoder);
+ gme_err = gme_seek(emu, int(where * 1000));
+ if (gme_err != nullptr)
+ g_warning("%s", gme_err);
+ decoder_command_finished(decoder);
+ }
+
+ if (gme_track_ended(emu))
+ break;
+ } while (cmd != DECODE_COMMAND_STOP);
+
+ gme_free_info(ti);
+ gme_delete(emu);
+}
+
+static bool
+gme_scan_file(const char *path_fs,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ char *path_container = get_container_name(path_fs);
+
+ Music_Emu *emu;
+ const char *gme_err =
+ gme_open_file(path_container, &emu, GME_SAMPLE_RATE);
+ g_free(path_container);
+ if (gme_err != nullptr) {
+ g_warning("%s", gme_err);
+ return false;
+ }
+
+ const int song_num = get_song_num(path_fs);
+
+ gme_info_t *ti;
+ gme_err = gme_track_info(emu, &ti, song_num);
+ if (gme_err != nullptr) {
+ g_warning("%s", gme_err);
+ gme_delete(emu);
+ return false;
+ }
+
+ assert(ti != nullptr);
+
+ if (ti->length > 0)
+ tag_handler_invoke_duration(handler, handler_ctx,
+ ti->length / 100);
+
+ if (ti->song != nullptr) {
+ if (gme_track_count(emu) > 1) {
+ /* start numbering subtunes from 1 */
+ char *tag_title =
+ g_strdup_printf("%s (%d/%d)",
+ ti->song, song_num + 1,
+ gme_track_count(emu));
+ tag_handler_invoke_tag(handler, handler_ctx,
+ TAG_TITLE, tag_title);
+ g_free(tag_title);
+ } else
+ tag_handler_invoke_tag(handler, handler_ctx,
+ TAG_TITLE, ti->song);
+ }
+
+ if (ti->author != nullptr)
+ tag_handler_invoke_tag(handler, handler_ctx,
+ TAG_ARTIST, ti->author);
+
+ if (ti->game != nullptr)
+ tag_handler_invoke_tag(handler, handler_ctx,
+ TAG_ALBUM, ti->game);
+
+ if (ti->comment != nullptr)
+ tag_handler_invoke_tag(handler, handler_ctx,
+ TAG_COMMENT, ti->comment);
+
+ if (ti->copyright != nullptr)
+ tag_handler_invoke_tag(handler, handler_ctx,
+ TAG_DATE, ti->copyright);
+
+ gme_free_info(ti);
+ gme_delete(emu);
+
+ return true;
+}
+
+static const char *const gme_suffixes[] = {
+ "ay", "gbs", "gym", "hes", "kss", "nsf",
+ "nsfe", "sap", "spc", "vgm", "vgz",
+ nullptr
+};
+
+extern const struct decoder_plugin gme_decoder_plugin;
+const struct decoder_plugin gme_decoder_plugin = {
+ "gme",
+ nullptr,
+ nullptr,
+ nullptr,
+ gme_file_decode,
+ gme_scan_file,
+ nullptr,
+ gme_container_scan,
+ gme_suffixes,
+ nullptr,
+};
diff --git a/src/decoder/GmeDecoderPlugin.hxx b/src/decoder/GmeDecoderPlugin.hxx
new file mode 100644
index 000000000..fba735d92
--- /dev/null
+++ b/src/decoder/GmeDecoderPlugin.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_DECODER_GME_HXX
+#define MPD_DECODER_GME_HXX
+
+extern const struct decoder_plugin gme_decoder_plugin;
+
+#endif
diff --git a/src/decoder/MadDecoderPlugin.cxx b/src/decoder/MadDecoderPlugin.cxx
new file mode 100644
index 000000000..29abfafbd
--- /dev/null
+++ b/src/decoder/MadDecoderPlugin.cxx
@@ -0,0 +1,1181 @@
+/*
+ * 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 "MadDecoderPlugin.hxx"
+#include "DecoderAPI.hxx"
+#include "conf.h"
+#include "TagId3.hxx"
+#include "TagRva2.hxx"
+#include "TagHandler.hxx"
+#include "CheckAudioFormat.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
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "mad"
+
+#define FRAMES_CUSHION 2000
+
+#define READ_BUFFER_SIZE 40960
+
+enum mp3_action {
+ DECODE_SKIP = -3,
+ DECODE_BREAK = -2,
+ DECODE_CONT = -1,
+ DECODE_OK = 0
+};
+
+enum muteframe {
+ MUTEFRAME_NONE,
+ MUTEFRAME_SKIP,
+ MUTEFRAME_SEEK
+};
+
+/* the number of samples of silence the decoder inserts at start */
+#define DECODERDELAY 529
+
+#define DEFAULT_GAPLESS_MP3_PLAYBACK true
+
+static bool gapless_playback;
+
+static inline int32_t
+mad_fixed_to_24_sample(mad_fixed_t sample)
+{
+ enum {
+ bits = 24,
+ MIN = -MAD_F_ONE,
+ MAX = MAD_F_ONE - 1
+ };
+
+ /* round */
+ sample = sample + (1L << (MAD_F_FRACBITS - bits));
+
+ /* clip */
+ if (gcc_unlikely(sample > MAX))
+ sample = MAX;
+ else if (gcc_unlikely(sample < MIN))
+ sample = MIN;
+
+ /* quantize */
+ return sample >> (MAD_F_FRACBITS + 1 - bits);
+}
+
+static void
+mad_fixed_to_24_buffer(int32_t *dest, const struct mad_synth *synth,
+ unsigned int start, unsigned int end,
+ unsigned int num_channels)
+{
+ unsigned int i, c;
+
+ for (i = start; i < end; ++i) {
+ for (c = 0; c < num_channels; ++c)
+ *dest++ = mad_fixed_to_24_sample(synth->pcm.samples[c][i]);
+ }
+}
+
+static bool
+mp3_plugin_init(gcc_unused const config_param &param)
+{
+ gapless_playback = config_get_bool(CONF_GAPLESS_MP3_PLAYBACK,
+ DEFAULT_GAPLESS_MP3_PLAYBACK);
+ return true;
+}
+
+#define MP3_DATA_OUTPUT_BUFFER_SIZE 2048
+
+struct MadDecoder {
+ struct mad_stream stream;
+ struct mad_frame frame;
+ struct mad_synth synth;
+ mad_timer_t timer;
+ unsigned char input_buffer[READ_BUFFER_SIZE];
+ int32_t output_buffer[MP3_DATA_OUTPUT_BUFFER_SIZE];
+ float total_time;
+ float elapsed_time;
+ float seek_where;
+ enum muteframe mute_frame;
+ long *frame_offsets;
+ mad_timer_t *times;
+ unsigned long highest_frame;
+ unsigned long max_frames;
+ unsigned long current_frame;
+ unsigned int drop_start_frames;
+ unsigned int drop_end_frames;
+ unsigned int drop_start_samples;
+ unsigned int drop_end_samples;
+ bool found_replay_gain;
+ bool found_xing;
+ bool found_first_frame;
+ bool decoded_first_frame;
+ unsigned long bit_rate;
+ struct decoder *decoder;
+ struct input_stream *input_stream;
+ enum mad_layer layer;
+
+ MadDecoder(struct decoder *decoder, struct input_stream *input_stream);
+ ~MadDecoder();
+
+ bool Seek(long offset);
+ bool FillBuffer();
+ void ParseId3(size_t tagsize, Tag **mpd_tag);
+ enum mp3_action DecodeNextFrameHeader(Tag **tag);
+ enum mp3_action DecodeNextFrame();
+
+ gcc_pure
+ goffset ThisFrameOffset() const;
+
+ gcc_pure
+ goffset RestIncludingThisFrame() const;
+
+ /**
+ * Attempt to calulcate the length of the song from filesize
+ */
+ void FileSizeToSongLength();
+
+ bool DecodeFirstFrame(Tag **tag);
+
+ gcc_pure
+ long TimeToFrame(double t) const;
+
+ void UpdateTimerNextFrame();
+
+ /**
+ * Sends the synthesized current frame via decoder_data().
+ */
+ enum decoder_command SendPCM(unsigned i, unsigned pcm_length);
+
+ /**
+ * Synthesize the current frame and send it via
+ * decoder_data().
+ */
+ enum decoder_command SyncAndSend();
+
+ bool Read();
+};
+
+MadDecoder::MadDecoder(struct decoder *_decoder,
+ struct input_stream *_input_stream)
+ :mute_frame(MUTEFRAME_NONE),
+ frame_offsets(nullptr),
+ times(nullptr),
+ highest_frame(0), max_frames(0), current_frame(0),
+ drop_start_frames(0), drop_end_frames(0),
+ drop_start_samples(0), drop_end_samples(0),
+ found_replay_gain(false), found_xing(false),
+ found_first_frame(false), decoded_first_frame(false),
+ decoder(_decoder), input_stream(_input_stream),
+ layer(mad_layer(0))
+{
+ mad_stream_init(&stream);
+ mad_stream_options(&stream, MAD_OPTION_IGNORECRC);
+ mad_frame_init(&frame);
+ mad_synth_init(&synth);
+ mad_timer_reset(&timer);
+}
+
+inline bool
+MadDecoder::Seek(long offset)
+{
+ if (!input_stream_lock_seek(input_stream, offset, SEEK_SET,
+ nullptr))
+ return false;
+
+ mad_stream_buffer(&stream, input_buffer, 0);
+ stream.error = MAD_ERROR_NONE;
+
+ return true;
+}
+
+inline bool
+MadDecoder::FillBuffer()
+{
+ size_t remaining, length;
+ unsigned char *dest;
+
+ if (stream.next_frame != nullptr) {
+ remaining = stream.bufend - stream.next_frame;
+ memmove(input_buffer, stream.next_frame, remaining);
+ dest = input_buffer + remaining;
+ length = READ_BUFFER_SIZE - remaining;
+ } else {
+ remaining = 0;
+ length = READ_BUFFER_SIZE;
+ dest = input_buffer;
+ }
+
+ /* we've exhausted the read buffer, so give up!, these potential
+ * mp3 frames are way too big, and thus unlikely to be mp3 frames */
+ if (length == 0)
+ return false;
+
+ length = decoder_read(decoder, input_stream, dest, length);
+ if (length == 0)
+ return false;
+
+ mad_stream_buffer(&stream, input_buffer, length + remaining);
+ stream.error = MAD_ERROR_NONE;
+
+ return true;
+}
+
+#ifdef HAVE_ID3TAG
+static bool
+parse_id3_replay_gain_info(struct replay_gain_info *replay_gain_info,
+ struct id3_tag *tag)
+{
+ int i;
+ char *key;
+ char *value;
+ struct id3_frame *frame;
+ bool found = false;
+
+ replay_gain_info_init(replay_gain_info);
+
+ for (i = 0; (frame = id3_tag_findframe(tag, "TXXX", i)); i++) {
+ if (frame->nfields < 3)
+ continue;
+
+ key = (char *)
+ id3_ucs4_latin1duplicate(id3_field_getstring
+ (&frame->fields[1]));
+ value = (char *)
+ id3_ucs4_latin1duplicate(id3_field_getstring
+ (&frame->fields[2]));
+
+ if (g_ascii_strcasecmp(key, "replaygain_track_gain") == 0) {
+ replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain = atof(value);
+ found = true;
+ } else if (g_ascii_strcasecmp(key, "replaygain_album_gain") == 0) {
+ replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain = atof(value);
+ found = true;
+ } else if (g_ascii_strcasecmp(key, "replaygain_track_peak") == 0) {
+ replay_gain_info->tuples[REPLAY_GAIN_TRACK].peak = atof(value);
+ found = true;
+ } else if (g_ascii_strcasecmp(key, "replaygain_album_peak") == 0) {
+ replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak = atof(value);
+ found = true;
+ }
+
+ free(key);
+ free(value);
+ }
+
+ return found ||
+ /* fall back on RVA2 if no replaygain tags found */
+ tag_rva2_parse(tag, replay_gain_info);
+}
+#endif
+
+#ifdef HAVE_ID3TAG
+static bool
+parse_id3_mixramp(char **mixramp_start, char **mixramp_end,
+ struct id3_tag *tag)
+{
+ int i;
+ char *key;
+ char *value;
+ struct id3_frame *frame;
+ bool found = false;
+
+ *mixramp_start = nullptr;
+ *mixramp_end = nullptr;
+
+ for (i = 0; (frame = id3_tag_findframe(tag, "TXXX", i)); i++) {
+ if (frame->nfields < 3)
+ continue;
+
+ key = (char *)
+ id3_ucs4_latin1duplicate(id3_field_getstring
+ (&frame->fields[1]));
+ value = (char *)
+ id3_ucs4_latin1duplicate(id3_field_getstring
+ (&frame->fields[2]));
+
+ if (g_ascii_strcasecmp(key, "mixramp_start") == 0) {
+ *mixramp_start = g_strdup(value);
+ found = true;
+ } else if (g_ascii_strcasecmp(key, "mixramp_end") == 0) {
+ *mixramp_end = g_strdup(value);
+ found = true;
+ }
+
+ free(key);
+ free(value);
+ }
+
+ return found;
+}
+#endif
+
+inline void
+MadDecoder::ParseId3(size_t tagsize, Tag **mpd_tag)
+{
+#ifdef HAVE_ID3TAG
+ struct id3_tag *id3_tag = nullptr;
+ id3_length_t count;
+ id3_byte_t const *id3_data;
+ id3_byte_t *allocated = nullptr;
+
+ count = stream.bufend - stream.this_frame;
+
+ if (tagsize <= count) {
+ id3_data = stream.this_frame;
+ mad_stream_skip(&(stream), tagsize);
+ } else {
+ allocated = (id3_byte_t *)g_malloc(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) {
+ g_debug("error parsing ID3 tag");
+ g_free(allocated);
+ return;
+ }
+
+ id3_data = allocated;
+ }
+
+ id3_tag = id3_tag_parse(id3_data, tagsize);
+ if (id3_tag == nullptr) {
+ g_free(allocated);
+ return;
+ }
+
+ if (mpd_tag) {
+ Tag *tmp_tag = tag_id3_import(id3_tag);
+ if (tmp_tag != nullptr) {
+ delete *mpd_tag;
+ *mpd_tag = tmp_tag;
+ }
+ }
+
+ if (decoder != nullptr) {
+ struct replay_gain_info rgi;
+ char *mixramp_start;
+ char *mixramp_end;
+
+ if (parse_id3_replay_gain_info(&rgi, id3_tag)) {
+ decoder_replay_gain(decoder, &rgi);
+ found_replay_gain = true;
+ }
+
+ if (parse_id3_mixramp(&mixramp_start, &mixramp_end, id3_tag))
+ decoder_mixramp(decoder, mixramp_start, mixramp_end);
+ }
+
+ id3_tag_delete(id3_tag);
+
+ g_free(allocated);
+#else /* !HAVE_ID3TAG */
+ (void)mpd_tag;
+
+ /* This code is enabled when libid3tag is disabled. Instead
+ of parsing the ID3 frame, it just skips it. */
+
+ size_t count = stream.bufend - stream.this_frame;
+
+ if (tagsize <= count) {
+ 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;
+ }
+ }
+#endif
+}
+
+#ifndef HAVE_ID3TAG
+/**
+ * This function emulates libid3tag when it is disabled. Instead of
+ * doing a real analyzation of the frame, it just checks whether the
+ * frame begins with the string "ID3". If so, it returns the length
+ * of the ID3 frame.
+ */
+static signed long
+id3_tag_query(const void *p0, size_t length)
+{
+ const char *p = (const char *)p0;
+
+ return length >= 10 && memcmp(p, "ID3", 3) == 0
+ ? (p[8] << 7) + p[9] + 10
+ : 0;
+}
+#endif /* !HAVE_ID3TAG */
+
+enum mp3_action
+MadDecoder::DecodeNextFrameHeader(Tag **tag)
+{
+ if ((stream.buffer == nullptr || stream.error == MAD_ERROR_BUFLEN) &&
+ !FillBuffer())
+ return DECODE_BREAK;
+
+ if (mad_header_decode(&frame.header, &stream)) {
+ if (stream.error == MAD_ERROR_LOSTSYNC && stream.this_frame) {
+ signed long tagsize = id3_tag_query(stream.this_frame,
+ stream.bufend -
+ stream.this_frame);
+
+ if (tagsize > 0) {
+ if (tag && !(*tag)) {
+ ParseId3((size_t)tagsize, tag);
+ } else {
+ mad_stream_skip(&stream, tagsize);
+ }
+ return DECODE_CONT;
+ }
+ }
+ if (MAD_RECOVERABLE(stream.error)) {
+ return DECODE_SKIP;
+ } else {
+ if (stream.error == MAD_ERROR_BUFLEN)
+ return DECODE_CONT;
+ else {
+ g_warning("unrecoverable frame level error "
+ "(%s).\n",
+ mad_stream_errorstr(&stream));
+ return DECODE_BREAK;
+ }
+ }
+ }
+
+ enum mad_layer new_layer = frame.header.layer;
+ if (layer == (mad_layer)0) {
+ if (new_layer != MAD_LAYER_II && new_layer != MAD_LAYER_III) {
+ /* Only layer 2 and 3 have been tested to work */
+ return DECODE_SKIP;
+ }
+
+ layer = new_layer;
+ } else if (new_layer != layer) {
+ /* Don't decode frames with a different layer than the first */
+ return DECODE_SKIP;
+ }
+
+ return DECODE_OK;
+}
+
+enum mp3_action
+MadDecoder::DecodeNextFrame()
+{
+ if ((stream.buffer == nullptr || stream.error == MAD_ERROR_BUFLEN) &&
+ !FillBuffer())
+ return DECODE_BREAK;
+
+ if (mad_frame_decode(&frame, &stream)) {
+ if (stream.error == MAD_ERROR_LOSTSYNC) {
+ signed long tagsize = id3_tag_query(stream.this_frame,
+ stream.bufend -
+ stream.this_frame);
+ if (tagsize > 0) {
+ mad_stream_skip(&stream, tagsize);
+ return DECODE_CONT;
+ }
+ }
+ if (MAD_RECOVERABLE(stream.error)) {
+ return DECODE_SKIP;
+ } else {
+ if (stream.error == MAD_ERROR_BUFLEN)
+ return DECODE_CONT;
+ else {
+ g_warning("unrecoverable frame level error "
+ "(%s).\n",
+ mad_stream_errorstr(&stream));
+ return DECODE_BREAK;
+ }
+ }
+ }
+
+ return DECODE_OK;
+}
+
+/* xing stuff stolen from alsaplayer, and heavily modified by jat */
+#define XI_MAGIC (('X' << 8) | 'i')
+#define NG_MAGIC (('n' << 8) | 'g')
+#define IN_MAGIC (('I' << 8) | 'n')
+#define FO_MAGIC (('f' << 8) | 'o')
+
+enum xing_magic {
+ XING_MAGIC_XING, /* VBR */
+ XING_MAGIC_INFO /* CBR */
+};
+
+struct xing {
+ long flags; /* valid fields (see below) */
+ unsigned long frames; /* total number of frames */
+ unsigned long bytes; /* total number of bytes */
+ unsigned char toc[100]; /* 100-point seek table */
+ long scale; /* VBR quality */
+ enum xing_magic magic; /* header magic */
+};
+
+enum {
+ XING_FRAMES = 0x00000001L,
+ XING_BYTES = 0x00000002L,
+ XING_TOC = 0x00000004L,
+ XING_SCALE = 0x00000008L
+};
+
+struct lame_version {
+ unsigned major;
+ unsigned minor;
+};
+
+struct lame {
+ char encoder[10]; /* 9 byte encoder name/version ("LAME3.97b") */
+ struct lame_version version; /* struct containing just the version */
+ float peak; /* replaygain peak */
+ float track_gain; /* replaygain track gain */
+ float album_gain; /* replaygain album gain */
+ int encoder_delay; /* # of added samples at start of mp3 */
+ int encoder_padding; /* # of added samples at end of mp3 */
+ int crc; /* CRC of the first 190 bytes of this frame */
+};
+
+static bool
+parse_xing(struct xing *xing, struct mad_bitptr *ptr, int *oldbitlen)
+{
+ unsigned long bits;
+ int bitlen;
+ int bitsleft;
+ int i;
+
+ bitlen = *oldbitlen;
+
+ if (bitlen < 16)
+ return false;
+
+ bits = mad_bit_read(ptr, 16);
+ bitlen -= 16;
+
+ if (bits == XI_MAGIC) {
+ if (bitlen < 16)
+ return false;
+
+ if (mad_bit_read(ptr, 16) != NG_MAGIC)
+ return false;
+
+ bitlen -= 16;
+ xing->magic = XING_MAGIC_XING;
+ } else if (bits == IN_MAGIC) {
+ if (bitlen < 16)
+ return false;
+
+ if (mad_bit_read(ptr, 16) != FO_MAGIC)
+ return false;
+
+ bitlen -= 16;
+ xing->magic = XING_MAGIC_INFO;
+ }
+ else if (bits == NG_MAGIC) xing->magic = XING_MAGIC_XING;
+ else if (bits == FO_MAGIC) xing->magic = XING_MAGIC_INFO;
+ else
+ return false;
+
+ if (bitlen < 32)
+ return false;
+ xing->flags = mad_bit_read(ptr, 32);
+ bitlen -= 32;
+
+ if (xing->flags & XING_FRAMES) {
+ if (bitlen < 32)
+ return false;
+ xing->frames = mad_bit_read(ptr, 32);
+ bitlen -= 32;
+ }
+
+ if (xing->flags & XING_BYTES) {
+ if (bitlen < 32)
+ return false;
+ xing->bytes = mad_bit_read(ptr, 32);
+ bitlen -= 32;
+ }
+
+ if (xing->flags & XING_TOC) {
+ if (bitlen < 800)
+ return false;
+ for (i = 0; i < 100; ++i) xing->toc[i] = mad_bit_read(ptr, 8);
+ bitlen -= 800;
+ }
+
+ if (xing->flags & XING_SCALE) {
+ if (bitlen < 32)
+ return false;
+ xing->scale = mad_bit_read(ptr, 32);
+ bitlen -= 32;
+ }
+
+ /* Make sure we consume no less than 120 bytes (960 bits) in hopes that
+ * the LAME tag is found there, and not right after the Xing header */
+ bitsleft = 960 - ((*oldbitlen) - bitlen);
+ if (bitsleft < 0)
+ return false;
+ else if (bitsleft > 0) {
+ mad_bit_read(ptr, bitsleft);
+ bitlen -= bitsleft;
+ }
+
+ *oldbitlen = bitlen;
+
+ return true;
+}
+
+static bool
+parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen)
+{
+ int adj = 0;
+ int name;
+ int orig;
+ int sign;
+ int gain;
+ int i;
+
+ /* Unlike the xing header, the lame tag has a fixed length. Fail if
+ * not all 36 bytes (288 bits) are there. */
+ if (*bitlen < 288)
+ return false;
+
+ for (i = 0; i < 9; i++)
+ lame->encoder[i] = (char)mad_bit_read(ptr, 8);
+ lame->encoder[9] = '\0';
+
+ *bitlen -= 72;
+
+ /* 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"))
+ return false;
+
+ if (sscanf(lame->encoder+4, "%u.%u",
+ &lame->version.major, &lame->version.minor) != 2)
+ return false;
+
+ g_debug("detected LAME version %i.%i (\"%s\")\n",
+ lame->version.major, lame->version.minor, lame->encoder);
+
+ /* The reference volume was changed from the 83dB used in the
+ * ReplayGain spec to 89dB in lame 3.95.1. Bump the gain for older
+ * versions, since everyone else uses 89dB instead of 83dB.
+ * Unfortunately, lame didn't differentiate between 3.95 and 3.95.1, so
+ * it's impossible to make the proper adjustment for 3.95.
+ * Fortunately, 3.95 was only out for about a day before 3.95.1 was
+ * released. -- tmz */
+ if (lame->version.major < 3 ||
+ (lame->version.major == 3 && lame->version.minor < 95))
+ adj = 6;
+
+ mad_bit_read(ptr, 16);
+
+ lame->peak = mad_f_todouble(mad_bit_read(ptr, 32) << 5); /* peak */
+ g_debug("LAME peak found: %f\n", lame->peak);
+
+ lame->track_gain = 0;
+ name = mad_bit_read(ptr, 3); /* gain name */
+ orig = mad_bit_read(ptr, 3); /* gain originator */
+ sign = mad_bit_read(ptr, 1); /* sign bit */
+ gain = mad_bit_read(ptr, 9); /* gain*10 */
+ if (gain && name == 1 && orig != 0) {
+ lame->track_gain = ((sign ? -gain : gain) / 10.0) + adj;
+ g_debug("LAME track gain found: %f\n", lame->track_gain);
+ }
+
+ /* tmz reports that this isn't currently written by any version of lame
+ * (as of 3.97). Since we have no way of testing it, don't use it.
+ * Wouldn't want to go blowing someone's ears just because we read it
+ * wrong. :P -- jat */
+ lame->album_gain = 0;
+#if 0
+ name = mad_bit_read(ptr, 3); /* gain name */
+ orig = mad_bit_read(ptr, 3); /* gain originator */
+ sign = mad_bit_read(ptr, 1); /* sign bit */
+ gain = mad_bit_read(ptr, 9); /* gain*10 */
+ if (gain && name == 2 && orig != 0) {
+ lame->album_gain = ((sign ? -gain : gain) / 10.0) + adj;
+ g_debug("LAME album gain found: %f\n", lame->track_gain);
+ }
+#else
+ mad_bit_read(ptr, 16);
+#endif
+
+ mad_bit_read(ptr, 16);
+
+ lame->encoder_delay = mad_bit_read(ptr, 12);
+ lame->encoder_padding = mad_bit_read(ptr, 12);
+
+ g_debug("encoder delay is %i, encoder padding is %i\n",
+ lame->encoder_delay, lame->encoder_padding);
+
+ mad_bit_read(ptr, 80);
+
+ lame->crc = mad_bit_read(ptr, 16);
+
+ *bitlen -= 216;
+
+ return true;
+}
+
+static inline float
+mp3_frame_duration(const struct mad_frame *frame)
+{
+ return mad_timer_count(frame->header.duration,
+ MAD_UNITS_MILLISECONDS) / 1000.0;
+}
+
+inline goffset
+MadDecoder::ThisFrameOffset() const
+{
+ goffset offset = input_stream_get_offset(input_stream);
+
+ if (stream.this_frame != nullptr)
+ offset -= stream.bufend - stream.this_frame;
+ else
+ offset -= stream.bufend - stream.buffer;
+
+ return offset;
+}
+
+inline goffset
+MadDecoder::RestIncludingThisFrame() const
+{
+ return input_stream_get_size(input_stream) - ThisFrameOffset();
+}
+
+inline void
+MadDecoder::FileSizeToSongLength()
+{
+ goffset rest = RestIncludingThisFrame();
+
+ if (rest > 0) {
+ float frame_duration = mp3_frame_duration(&frame);
+
+ total_time = (rest * 8.0) / frame.header.bitrate;
+ max_frames = total_time / frame_duration + FRAMES_CUSHION;
+ } else {
+ max_frames = FRAMES_CUSHION;
+ total_time = 0;
+ }
+}
+
+inline bool
+MadDecoder::DecodeFirstFrame(Tag **tag)
+{
+ struct xing xing;
+ struct lame lame;
+ struct mad_bitptr ptr;
+ int bitlen;
+ enum mp3_action ret;
+
+ /* stfu gcc */
+ memset(&xing, 0, sizeof(struct xing));
+ xing.flags = 0;
+
+ while (true) {
+ do {
+ ret = DecodeNextFrameHeader(tag);
+ } while (ret == DECODE_CONT);
+ if (ret == DECODE_BREAK)
+ return false;
+ if (ret == DECODE_SKIP) continue;
+
+ do {
+ ret = DecodeNextFrame();
+ } while (ret == DECODE_CONT);
+ if (ret == DECODE_BREAK)
+ return false;
+ if (ret == DECODE_OK) break;
+ }
+
+ ptr = stream.anc_ptr;
+ bitlen = stream.anc_bitlen;
+
+ FileSizeToSongLength();
+
+ /*
+ * if an xing tag exists, use that!
+ */
+ if (parse_xing(&xing, &ptr, &bitlen)) {
+ found_xing = true;
+ mute_frame = MUTEFRAME_SKIP;
+
+ if ((xing.flags & XING_FRAMES) && xing.frames) {
+ mad_timer_t duration = frame.header.duration;
+ mad_timer_multiply(&duration, xing.frames);
+ total_time = ((float)mad_timer_count(duration, MAD_UNITS_MILLISECONDS)) / 1000;
+ max_frames = xing.frames;
+ }
+
+ if (parse_lame(&lame, &ptr, &bitlen)) {
+ if (gapless_playback &&
+ input_stream_is_seekable(input_stream)) {
+ drop_start_samples = lame.encoder_delay +
+ DECODERDELAY;
+ drop_end_samples = lame.encoder_padding;
+ }
+
+ /* Album gain isn't currently used. See comment in
+ * parse_lame() for details. -- jat */
+ if (decoder != nullptr && !found_replay_gain &&
+ lame.track_gain) {
+ struct replay_gain_info rgi;
+ replay_gain_info_init(&rgi);
+ rgi.tuples[REPLAY_GAIN_TRACK].gain = lame.track_gain;
+ rgi.tuples[REPLAY_GAIN_TRACK].peak = lame.peak;
+ decoder_replay_gain(decoder, &rgi);
+ }
+ }
+ }
+
+ if (!max_frames)
+ return false;
+
+ if (max_frames > 8 * 1024 * 1024) {
+ g_warning("mp3 file header indicates too many frames: %lu\n",
+ max_frames);
+ return false;
+ }
+
+ frame_offsets = new long[max_frames];
+ times = new mad_timer_t[max_frames];
+
+ return true;
+}
+
+MadDecoder::~MadDecoder()
+{
+ mad_synth_finish(&synth);
+ mad_frame_finish(&frame);
+ mad_stream_finish(&stream);
+
+ delete[] frame_offsets;
+ delete[] times;
+}
+
+/* this is primarily used for getting total time for tags */
+static int
+mad_decoder_total_file_time(struct input_stream *is)
+{
+ MadDecoder data(nullptr, is);
+ return data.DecodeFirstFrame(nullptr)
+ ? data.total_time + 0.5
+ : -1;
+}
+
+long
+MadDecoder::TimeToFrame(double t) const
+{
+ unsigned long i;
+
+ for (i = 0; i < highest_frame; ++i) {
+ double frame_time =
+ mad_timer_count(times[i],
+ MAD_UNITS_MILLISECONDS) / 1000.;
+ if (frame_time >= t)
+ break;
+ }
+
+ return i;
+}
+
+void
+MadDecoder::UpdateTimerNextFrame()
+{
+ if (current_frame >= highest_frame) {
+ /* record this frame's properties in frame_offsets
+ (for seeking) and times */
+ bit_rate = frame.header.bitrate;
+
+ if (current_frame >= max_frames)
+ /* cap current_frame */
+ current_frame = max_frames - 1;
+ else
+ highest_frame++;
+
+ frame_offsets[current_frame] = ThisFrameOffset();
+
+ mad_timer_add(&timer, frame.header.duration);
+ times[current_frame] = timer;
+ } else
+ /* get the new timer value from "times" */
+ timer = times[current_frame];
+
+ current_frame++;
+ elapsed_time = mad_timer_count(timer, MAD_UNITS_MILLISECONDS) / 1000.0;
+}
+
+enum decoder_command
+MadDecoder::SendPCM(unsigned i, unsigned pcm_length)
+{
+ unsigned max_samples;
+
+ max_samples = sizeof(output_buffer) /
+ sizeof(output_buffer[0]) /
+ MAD_NCHANNELS(&frame.header);
+
+ while (i < pcm_length) {
+ enum decoder_command cmd;
+ unsigned int num_samples = pcm_length - i;
+ if (num_samples > max_samples)
+ num_samples = max_samples;
+
+ i += num_samples;
+
+ mad_fixed_to_24_buffer(output_buffer, &synth,
+ i - num_samples, i,
+ MAD_NCHANNELS(&frame.header));
+ num_samples *= MAD_NCHANNELS(&frame.header);
+
+ cmd = decoder_data(decoder, input_stream, output_buffer,
+ sizeof(output_buffer[0]) * num_samples,
+ bit_rate / 1000);
+ if (cmd != DECODE_COMMAND_NONE)
+ return cmd;
+ }
+
+ return DECODE_COMMAND_NONE;
+}
+
+inline enum decoder_command
+MadDecoder::SyncAndSend()
+{
+ mad_synth_frame(&synth, &frame);
+
+ if (!found_first_frame) {
+ unsigned int samples_per_frame = synth.pcm.length;
+ drop_start_frames = drop_start_samples / samples_per_frame;
+ drop_end_frames = drop_end_samples / samples_per_frame;
+ drop_start_samples = drop_start_samples % samples_per_frame;
+ drop_end_samples = drop_end_samples % samples_per_frame;
+ found_first_frame = true;
+ }
+
+ if (drop_start_frames > 0) {
+ drop_start_frames--;
+ return DECODE_COMMAND_NONE;
+ } else if ((drop_end_frames > 0) &&
+ (current_frame == (max_frames + 1 - drop_end_frames))) {
+ /* stop decoding, effectively dropping all remaining
+ frames */
+ return DECODE_COMMAND_STOP;
+ }
+
+ unsigned i = 0;
+ if (!decoded_first_frame) {
+ i = drop_start_samples;
+ decoded_first_frame = true;
+ }
+
+ unsigned pcm_length = synth.pcm.length;
+ if (drop_end_samples &&
+ (current_frame == max_frames - drop_end_frames)) {
+ if (drop_end_samples >= pcm_length)
+ pcm_length = 0;
+ else
+ pcm_length -= drop_end_samples;
+ }
+
+ enum decoder_command cmd = SendPCM(i, pcm_length);
+ if (cmd != DECODE_COMMAND_NONE)
+ return cmd;
+
+ if (drop_end_samples &&
+ (current_frame == max_frames - drop_end_frames))
+ /* stop decoding, effectively dropping
+ * all remaining samples */
+ return DECODE_COMMAND_STOP;
+
+ return DECODE_COMMAND_NONE;
+}
+
+inline bool
+MadDecoder::Read()
+{
+ enum mp3_action ret;
+ enum decoder_command cmd;
+
+ UpdateTimerNextFrame();
+
+ switch (mute_frame) {
+ case MUTEFRAME_SKIP:
+ mute_frame = MUTEFRAME_NONE;
+ break;
+ case MUTEFRAME_SEEK:
+ if (elapsed_time >= seek_where)
+ mute_frame = MUTEFRAME_NONE;
+ break;
+ case MUTEFRAME_NONE:
+ cmd = SyncAndSend();
+ if (cmd == DECODE_COMMAND_SEEK) {
+ unsigned long j;
+
+ assert(input_stream_is_seekable(input_stream));
+
+ j = TimeToFrame(decoder_seek_where(decoder));
+ if (j < highest_frame) {
+ if (Seek(frame_offsets[j])) {
+ current_frame = j;
+ decoder_command_finished(decoder);
+ } else
+ decoder_seek_error(decoder);
+ } else {
+ seek_where = decoder_seek_where(decoder);
+ mute_frame = MUTEFRAME_SEEK;
+ decoder_command_finished(decoder);
+ }
+ } else if (cmd != DECODE_COMMAND_NONE)
+ return false;
+ }
+
+ while (true) {
+ bool skip = false;
+
+ do {
+ Tag *tag = nullptr;
+
+ ret = DecodeNextFrameHeader(&tag);
+
+ if (tag != nullptr) {
+ decoder_tag(decoder, input_stream,
+ std::move(*tag));
+ delete tag;
+ }
+ } while (ret == DECODE_CONT);
+ if (ret == DECODE_BREAK)
+ return false;
+ else if (ret == DECODE_SKIP)
+ skip = true;
+
+ if (mute_frame == MUTEFRAME_NONE) {
+ do {
+ ret = DecodeNextFrame();
+ } while (ret == DECODE_CONT);
+ if (ret == DECODE_BREAK)
+ return false;
+ }
+
+ if (!skip && ret == DECODE_OK)
+ break;
+ }
+
+ return ret != DECODE_BREAK;
+}
+
+static void
+mp3_decode(struct decoder *decoder, struct input_stream *input_stream)
+{
+ MadDecoder data(decoder, input_stream);
+
+ Tag *tag = nullptr;
+ if (!data.DecodeFirstFrame(&tag)) {
+ delete tag;
+
+ if (decoder_get_command(decoder) == DECODE_COMMAND_NONE)
+ g_warning
+ ("Input does not appear to be a mp3 bit stream.\n");
+ return;
+ }
+
+ AudioFormat audio_format;
+ GError *error = nullptr;
+ if (!audio_format_init_checked(audio_format,
+ data.frame.header.samplerate,
+ SampleFormat::S24_P32,
+ MAD_NCHANNELS(&data.frame.header),
+ &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+
+ delete tag;
+ return;
+ }
+
+ decoder_initialized(decoder, audio_format,
+ input_stream_is_seekable(input_stream),
+ data.total_time);
+
+ if (tag != nullptr) {
+ decoder_tag(decoder, input_stream, std::move(*tag));
+ delete tag;
+ }
+
+ while (data.Read()) {}
+}
+
+static bool
+mad_decoder_scan_stream(struct input_stream *is,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ int total_time;
+
+ total_time = mad_decoder_total_file_time(is);
+ if (total_time < 0)
+ return false;
+
+ tag_handler_invoke_duration(handler, handler_ctx, total_time);
+ return true;
+}
+
+static const char *const mp3_suffixes[] = { "mp3", "mp2", nullptr };
+static const char *const mp3_mime_types[] = { "audio/mpeg", nullptr };
+
+const struct decoder_plugin mad_decoder_plugin = {
+ "mad",
+ mp3_plugin_init,
+ nullptr,
+ mp3_decode,
+ nullptr,
+ nullptr,
+ mad_decoder_scan_stream,
+ nullptr,
+ mp3_suffixes,
+ mp3_mime_types,
+};
diff --git a/src/decoder/MadDecoderPlugin.hxx b/src/decoder/MadDecoderPlugin.hxx
new file mode 100644
index 000000000..c7a77304c
--- /dev/null
+++ b/src/decoder/MadDecoderPlugin.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_DECODER_MAD_HXX
+#define MPD_DECODER_MAD_HXX
+
+extern const struct decoder_plugin mad_decoder_plugin;
+
+#endif
diff --git a/src/decoder/MikmodDecoderPlugin.cxx b/src/decoder/MikmodDecoderPlugin.cxx
new file mode 100644
index 000000000..f9896ef98
--- /dev/null
+++ b/src/decoder/MikmodDecoderPlugin.cxx
@@ -0,0 +1,242 @@
+/*
+ * 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 "MikmodDecoderPlugin.hxx"
+#include "DecoderAPI.hxx"
+#include "mpd_error.h"
+#include "TagHandler.hxx"
+
+#include <glib.h>
+#include <mikmod.h>
+#include <assert.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "mikmod"
+
+/* this is largely copied from alsaplayer */
+
+static constexpr size_t MIKMOD_FRAME_SIZE = 4096;
+
+static BOOL
+mikmod_mpd_init(void)
+{
+ return VC_Init();
+}
+
+static void
+mikmod_mpd_exit(void)
+{
+ VC_Exit();
+}
+
+static void
+mikmod_mpd_update(void)
+{
+}
+
+static BOOL
+mikmod_mpd_is_present(void)
+{
+ return true;
+}
+
+static char drv_name[] = PACKAGE_NAME;
+static char drv_version[] = VERSION;
+
+#if (LIBMIKMOD_VERSION > 0x030106)
+static char drv_alias[] = PACKAGE;
+#endif
+
+static MDRIVER drv_mpd = {
+ nullptr,
+ drv_name,
+ drv_version,
+ 0,
+ 255,
+#if (LIBMIKMOD_VERSION > 0x030106)
+ drv_alias,
+#if (LIBMIKMOD_VERSION >= 0x030200)
+ nullptr, /* CmdLineHelp */
+#endif
+ nullptr, /* CommandLine */
+#endif
+ mikmod_mpd_is_present,
+ VC_SampleLoad,
+ VC_SampleUnload,
+ VC_SampleSpace,
+ VC_SampleLength,
+ mikmod_mpd_init,
+ mikmod_mpd_exit,
+ nullptr,
+ VC_SetNumVoices,
+ VC_PlayStart,
+ VC_PlayStop,
+ mikmod_mpd_update,
+ nullptr,
+ VC_VoiceSetVolume,
+ VC_VoiceGetVolume,
+ VC_VoiceSetFrequency,
+ VC_VoiceGetFrequency,
+ VC_VoiceSetPanning,
+ VC_VoiceGetPanning,
+ VC_VoicePlay,
+ VC_VoiceStop,
+ VC_VoiceStopped,
+ VC_VoiceGetPosition,
+ VC_VoiceRealVolume
+};
+
+static unsigned mikmod_sample_rate;
+
+static bool
+mikmod_decoder_init(const config_param &param)
+{
+ static char params[] = "";
+
+ mikmod_sample_rate = param.GetBlockValue("sample_rate", 44100u);
+ if (!audio_valid_sample_rate(mikmod_sample_rate))
+ MPD_ERROR("Invalid sample rate in line %d: %u",
+ param.line, mikmod_sample_rate);
+
+ md_device = 0;
+ md_reverb = 0;
+
+ MikMod_RegisterDriver(&drv_mpd);
+ MikMod_RegisterAllLoaders();
+
+ md_pansep = 64;
+ md_mixfreq = mikmod_sample_rate;
+ md_mode = (DMODE_SOFT_MUSIC | DMODE_INTERP | DMODE_STEREO |
+ DMODE_16BITS);
+
+ if (MikMod_Init(params)) {
+ g_warning("Could not init MikMod: %s\n",
+ MikMod_strerror(MikMod_errno));
+ return false;
+ }
+
+ return true;
+}
+
+static void
+mikmod_decoder_finish(void)
+{
+ MikMod_Exit();
+}
+
+static void
+mikmod_decoder_file_decode(struct decoder *decoder, const char *path_fs)
+{
+ char *path2;
+ MODULE *handle;
+ int ret;
+ SBYTE buffer[MIKMOD_FRAME_SIZE];
+ enum decoder_command cmd = DECODE_COMMAND_NONE;
+
+ path2 = g_strdup(path_fs);
+ handle = Player_Load(path2, 128, 0);
+ g_free(path2);
+
+ if (handle == nullptr) {
+ g_warning("failed to open mod: %s", path_fs);
+ return;
+ }
+
+ /* Prevent module from looping forever */
+ handle->loop = 0;
+
+ const AudioFormat audio_format(mikmod_sample_rate, SampleFormat::S16, 2);
+ assert(audio_format.IsValid());
+
+ decoder_initialized(decoder, audio_format, false, 0);
+
+ Player_Start(handle);
+ while (cmd == DECODE_COMMAND_NONE && Player_Active()) {
+ ret = VC_WriteBytes(buffer, sizeof(buffer));
+ cmd = decoder_data(decoder, nullptr, buffer, ret, 0);
+ }
+
+ Player_Stop();
+ Player_Free(handle);
+}
+
+static bool
+mikmod_decoder_scan_file(const char *path_fs,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ char *path2 = g_strdup(path_fs);
+ MODULE *handle = Player_Load(path2, 128, 0);
+
+ if (handle == nullptr) {
+ g_free(path2);
+ g_debug("Failed to open file: %s", path_fs);
+ return false;
+
+ }
+
+ Player_Free(handle);
+
+ char *title = Player_LoadTitle(path2);
+ g_free(path2);
+
+ if (title != nullptr) {
+ tag_handler_invoke_tag(handler, handler_ctx,
+ TAG_TITLE, title);
+#if (LIBMIKMOD_VERSION >= 0x030200)
+ MikMod_free(title);
+#else
+ free(title);
+#endif
+ }
+
+ return true;
+}
+
+static const char *const mikmod_decoder_suffixes[] = {
+ "amf",
+ "dsm",
+ "far",
+ "gdm",
+ "imf",
+ "it",
+ "med",
+ "mod",
+ "mtm",
+ "s3m",
+ "stm",
+ "stx",
+ "ult",
+ "uni",
+ "xm",
+ nullptr
+};
+
+const struct decoder_plugin mikmod_decoder_plugin = {
+ "mikmod",
+ mikmod_decoder_init,
+ mikmod_decoder_finish,
+ nullptr,
+ mikmod_decoder_file_decode,
+ mikmod_decoder_scan_file,
+ nullptr,
+ nullptr,
+ mikmod_decoder_suffixes,
+ nullptr,
+};
diff --git a/src/decoder/MikmodDecoderPlugin.hxx b/src/decoder/MikmodDecoderPlugin.hxx
new file mode 100644
index 000000000..dd3b1389e
--- /dev/null
+++ b/src/decoder/MikmodDecoderPlugin.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_DECODER_MIKMOD_HXX
+#define MPD_DECODER_MIKMOD_HXX
+
+extern const struct decoder_plugin mikmod_decoder_plugin;
+
+#endif
diff --git a/src/decoder/ModplugDecoderPlugin.cxx b/src/decoder/ModplugDecoderPlugin.cxx
new file mode 100644
index 000000000..b95736bf8
--- /dev/null
+++ b/src/decoder/ModplugDecoderPlugin.cxx
@@ -0,0 +1,200 @@
+/*
+ * 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 "ModplugDecoderPlugin.hxx"
+#include "DecoderAPI.hxx"
+#include "TagHandler.hxx"
+
+#include <glib.h>
+#include <modplug.h>
+#include <assert.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "modplug"
+
+static constexpr size_t MODPLUG_FRAME_SIZE = 4096;
+static constexpr size_t MODPLUG_PREALLOC_BLOCK = 256 * 1024;
+static constexpr size_t MODPLUG_READ_BLOCK = 128 * 1024;
+static constexpr goffset MODPLUG_FILE_LIMIT = 100 * 1024 * 1024;
+
+static GByteArray *
+mod_loadfile(struct decoder *decoder, struct input_stream *is)
+{
+ const goffset size = input_stream_get_size(is);
+
+ if (size == 0) {
+ g_warning("file is empty");
+ return nullptr;
+ }
+
+ if (size > MODPLUG_FILE_LIMIT) {
+ g_warning("file too large");
+ return nullptr;
+ }
+
+ //known/unknown size, preallocate array, lets read in chunks
+ GByteArray *bdatas;
+ if (size > 0) {
+ bdatas = g_byte_array_sized_new(size);
+ } else {
+ bdatas = g_byte_array_sized_new(MODPLUG_PREALLOC_BLOCK);
+ }
+
+ unsigned char *data = (unsigned char *)g_malloc(MODPLUG_READ_BLOCK);
+
+ while (true) {
+ size_t ret = decoder_read(decoder, is, data,
+ MODPLUG_READ_BLOCK);
+ if (ret == 0) {
+ if (input_stream_lock_eof(is))
+ /* end of file */
+ break;
+
+ /* I/O error - skip this song */
+ g_free(data);
+ g_byte_array_free(bdatas, true);
+ return nullptr;
+ }
+
+ if (goffset(bdatas->len + ret) > MODPLUG_FILE_LIMIT) {
+ g_warning("stream too large\n");
+ g_free(data);
+ g_byte_array_free(bdatas, TRUE);
+ return nullptr;
+ }
+
+ g_byte_array_append(bdatas, data, ret);
+ }
+
+ g_free(data);
+
+ return bdatas;
+}
+
+static void
+mod_decode(struct decoder *decoder, struct input_stream *is)
+{
+ ModPlugFile *f;
+ ModPlug_Settings settings;
+ GByteArray *bdatas;
+ int ret;
+ char audio_buffer[MODPLUG_FRAME_SIZE];
+ enum decoder_command cmd = DECODE_COMMAND_NONE;
+
+ bdatas = mod_loadfile(decoder, is);
+
+ if (!bdatas) {
+ g_warning("could not load stream\n");
+ return;
+ }
+
+ ModPlug_GetSettings(&settings);
+ /* alter setting */
+ settings.mResamplingMode = MODPLUG_RESAMPLE_FIR; /* RESAMP */
+ settings.mChannels = 2;
+ settings.mBits = 16;
+ settings.mFrequency = 44100;
+ /* insert more setting changes here */
+ ModPlug_SetSettings(&settings);
+
+ f = ModPlug_Load(bdatas->data, bdatas->len);
+ g_byte_array_free(bdatas, TRUE);
+ if (!f) {
+ g_warning("could not decode stream\n");
+ return;
+ }
+
+ static constexpr AudioFormat audio_format(44100, SampleFormat::S16, 2);
+ assert(audio_format.IsValid());
+
+ decoder_initialized(decoder, audio_format,
+ input_stream_is_seekable(is),
+ ModPlug_GetLength(f) / 1000.0);
+
+ do {
+ ret = ModPlug_Read(f, audio_buffer, MODPLUG_FRAME_SIZE);
+ if (ret <= 0)
+ break;
+
+ cmd = decoder_data(decoder, nullptr,
+ audio_buffer, ret,
+ 0);
+
+ if (cmd == DECODE_COMMAND_SEEK) {
+ float where = decoder_seek_where(decoder);
+
+ ModPlug_Seek(f, (int)(where * 1000.0));
+
+ decoder_command_finished(decoder);
+ }
+
+ } while (cmd != DECODE_COMMAND_STOP);
+
+ ModPlug_Unload(f);
+}
+
+static bool
+modplug_scan_stream(struct input_stream *is,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ ModPlugFile *f;
+ GByteArray *bdatas;
+
+ bdatas = mod_loadfile(nullptr, is);
+ if (!bdatas)
+ return false;
+
+ f = ModPlug_Load(bdatas->data, bdatas->len);
+ g_byte_array_free(bdatas, TRUE);
+ if (f == nullptr)
+ return false;
+
+ tag_handler_invoke_duration(handler, handler_ctx,
+ ModPlug_GetLength(f) / 1000);
+
+ const char *title = ModPlug_GetName(f);
+ if (title != nullptr)
+ tag_handler_invoke_tag(handler, handler_ctx,
+ TAG_TITLE, title);
+
+ ModPlug_Unload(f);
+
+ return true;
+}
+
+static const char *const mod_suffixes[] = {
+ "669", "amf", "ams", "dbm", "dfm", "dsm", "far", "it",
+ "med", "mdl", "mod", "mtm", "mt2", "okt", "s3m", "stm",
+ "ult", "umx", "xm",
+ nullptr
+};
+
+const struct decoder_plugin modplug_decoder_plugin = {
+ "modplug",
+ nullptr,
+ nullptr,
+ mod_decode,
+ nullptr,
+ nullptr,
+ modplug_scan_stream,
+ nullptr,
+ mod_suffixes,
+ nullptr,
+};
diff --git a/src/decoder/ModplugDecoderPlugin.hxx b/src/decoder/ModplugDecoderPlugin.hxx
new file mode 100644
index 000000000..fefb02b05
--- /dev/null
+++ b/src/decoder/ModplugDecoderPlugin.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_DECODER_MODPLUG_HXX
+#define MPD_DECODER_MODPLUG_HXX
+
+extern const struct decoder_plugin modplug_decoder_plugin;
+
+#endif
diff --git a/src/decoder/MpcdecDecoderPlugin.cxx b/src/decoder/MpcdecDecoderPlugin.cxx
new file mode 100644
index 000000000..cfb9c034b
--- /dev/null
+++ b/src/decoder/MpcdecDecoderPlugin.cxx
@@ -0,0 +1,277 @@
+/*
+ * 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 "MpcdecDecoderPlugin.hxx"
+#include "DecoderAPI.hxx"
+#include "CheckAudioFormat.hxx"
+#include "TagHandler.hxx"
+
+#include <mpc/mpcdec.h>
+
+#include <glib.h>
+#include <assert.h>
+#include <unistd.h>
+#include <math.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "mpcdec"
+
+struct mpc_decoder_data {
+ struct input_stream *is;
+ struct decoder *decoder;
+};
+
+static mpc_int32_t
+mpc_read_cb(mpc_reader *reader, void *ptr, mpc_int32_t size)
+{
+ struct mpc_decoder_data *data =
+ (struct mpc_decoder_data *)reader->data;
+
+ return decoder_read(data->decoder, data->is, ptr, size);
+}
+
+static mpc_bool_t
+mpc_seek_cb(mpc_reader *reader, mpc_int32_t offset)
+{
+ struct mpc_decoder_data *data =
+ (struct mpc_decoder_data *)reader->data;
+
+ return input_stream_lock_seek(data->is, offset, SEEK_SET, nullptr);
+}
+
+static mpc_int32_t
+mpc_tell_cb(mpc_reader *reader)
+{
+ struct mpc_decoder_data *data =
+ (struct mpc_decoder_data *)reader->data;
+
+ return (long)input_stream_get_offset(data->is);
+}
+
+static mpc_bool_t
+mpc_canseek_cb(mpc_reader *reader)
+{
+ struct mpc_decoder_data *data =
+ (struct mpc_decoder_data *)reader->data;
+
+ return input_stream_is_seekable(data->is);
+}
+
+static mpc_int32_t
+mpc_getsize_cb(mpc_reader *reader)
+{
+ struct mpc_decoder_data *data =
+ (struct mpc_decoder_data *)reader->data;
+
+ return input_stream_get_size(data->is);
+}
+
+/* this _looks_ performance-critical, don't de-inline -- eric */
+static inline int32_t
+mpc_to_mpd_sample(MPC_SAMPLE_FORMAT sample)
+{
+ /* only doing 16-bit audio for now */
+ int32_t val;
+
+ enum {
+ bits = 24,
+ };
+
+ const int clip_min = -1 << (bits - 1);
+ const int clip_max = (1 << (bits - 1)) - 1;
+
+#ifdef MPC_FIXED_POINT
+ const int shift = bits - MPC_FIXED_POINT_SCALE_SHIFT;
+
+ if (shift < 0)
+ val = sample >> -shift;
+ else
+ val = sample << shift;
+#else
+ const int float_scale = 1 << (bits - 1);
+
+ val = sample * float_scale;
+#endif
+
+ if (val < clip_min)
+ val = clip_min;
+ else if (val > clip_max)
+ val = clip_max;
+
+ return val;
+}
+
+static void
+mpc_to_mpd_buffer(int32_t *dest, const MPC_SAMPLE_FORMAT *src,
+ unsigned num_samples)
+{
+ while (num_samples-- > 0)
+ *dest++ = mpc_to_mpd_sample(*src++);
+}
+
+static void
+mpcdec_decode(struct decoder *mpd_decoder, struct input_stream *is)
+{
+ MPC_SAMPLE_FORMAT sample_buffer[MPC_DECODER_BUFFER_LENGTH];
+
+ struct mpc_decoder_data data;
+ data.is = is;
+ data.decoder = mpd_decoder;
+
+ mpc_reader reader;
+ reader.read = mpc_read_cb;
+ reader.seek = mpc_seek_cb;
+ reader.tell = mpc_tell_cb;
+ reader.get_size = mpc_getsize_cb;
+ reader.canseek = mpc_canseek_cb;
+ reader.data = &data;
+
+ mpc_demux *demux = mpc_demux_init(&reader);
+ if (demux == nullptr) {
+ if (decoder_get_command(mpd_decoder) != DECODE_COMMAND_STOP)
+ g_warning("Not a valid musepack stream");
+ return;
+ }
+
+ mpc_streaminfo info;
+ mpc_demux_get_info(demux, &info);
+
+ GError *error = nullptr;
+ AudioFormat audio_format;
+ if (!audio_format_init_checked(audio_format, info.sample_freq,
+ SampleFormat::S24_P32,
+ info.channels, &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ mpc_demux_exit(demux);
+ return;
+ }
+
+ struct replay_gain_info replay_gain_info;
+ replay_gain_info_init(&replay_gain_info);
+ replay_gain_info.tuples[REPLAY_GAIN_ALBUM].gain = MPC_OLD_GAIN_REF - (info.gain_album / 256.);
+ replay_gain_info.tuples[REPLAY_GAIN_ALBUM].peak = pow(10, info.peak_album / 256. / 20) / 32767;
+ replay_gain_info.tuples[REPLAY_GAIN_TRACK].gain = MPC_OLD_GAIN_REF - (info.gain_title / 256.);
+ replay_gain_info.tuples[REPLAY_GAIN_TRACK].peak = pow(10, info.peak_title / 256. / 20) / 32767;
+
+ decoder_replay_gain(mpd_decoder, &replay_gain_info);
+
+ decoder_initialized(mpd_decoder, audio_format,
+ input_stream_is_seekable(is),
+ mpc_streaminfo_get_length(&info));
+
+ enum decoder_command cmd = DECODE_COMMAND_NONE;
+ do {
+ if (cmd == DECODE_COMMAND_SEEK) {
+ mpc_int64_t where = decoder_seek_where(mpd_decoder) *
+ audio_format.sample_rate;
+ bool success;
+
+ success = mpc_demux_seek_sample(demux, where)
+ == MPC_STATUS_OK;
+ if (success)
+ decoder_command_finished(mpd_decoder);
+ else
+ decoder_seek_error(mpd_decoder);
+ }
+
+ mpc_uint32_t vbr_update_bits = 0;
+
+ mpc_frame_info frame;
+ frame.buffer = (MPC_SAMPLE_FORMAT *)sample_buffer;
+ mpc_status status = mpc_demux_decode(demux, &frame);
+ if (status != MPC_STATUS_OK) {
+ g_warning("Failed to decode sample");
+ break;
+ }
+
+ if (frame.bits == -1)
+ break;
+
+ mpc_uint32_t ret = frame.samples;
+ ret *= info.channels;
+
+ int32_t chunk[G_N_ELEMENTS(sample_buffer)];
+ mpc_to_mpd_buffer(chunk, sample_buffer, ret);
+
+ long bit_rate = vbr_update_bits * audio_format.sample_rate
+ / 1152 / 1000;
+
+ cmd = decoder_data(mpd_decoder, is,
+ chunk, ret * sizeof(chunk[0]),
+ bit_rate);
+ } while (cmd != DECODE_COMMAND_STOP);
+
+ mpc_demux_exit(demux);
+}
+
+static float
+mpcdec_get_file_duration(struct input_stream *is)
+{
+ struct mpc_decoder_data data;
+ data.is = is;
+ data.decoder = nullptr;
+
+ mpc_reader reader;
+ reader.read = mpc_read_cb;
+ reader.seek = mpc_seek_cb;
+ reader.tell = mpc_tell_cb;
+ reader.get_size = mpc_getsize_cb;
+ reader.canseek = mpc_canseek_cb;
+ reader.data = &data;
+
+ mpc_demux *demux = mpc_demux_init(&reader);
+ if (demux == nullptr)
+ return -1;
+
+ mpc_streaminfo info;
+ mpc_demux_get_info(demux, &info);
+ mpc_demux_exit(demux);
+
+ return mpc_streaminfo_get_length(&info);
+}
+
+static bool
+mpcdec_scan_stream(struct input_stream *is,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ float total_time = mpcdec_get_file_duration(is);
+
+ if (total_time < 0)
+ return false;
+
+ tag_handler_invoke_duration(handler, handler_ctx, total_time);
+ return true;
+}
+
+static const char *const mpcdec_suffixes[] = { "mpc", nullptr };
+
+const struct decoder_plugin mpcdec_decoder_plugin = {
+ "mpcdec",
+ nullptr,
+ nullptr,
+ mpcdec_decode,
+ nullptr,
+ nullptr,
+ mpcdec_scan_stream,
+ nullptr,
+ mpcdec_suffixes,
+ nullptr,
+};
diff --git a/src/decoder/MpcdecDecoderPlugin.hxx b/src/decoder/MpcdecDecoderPlugin.hxx
new file mode 100644
index 000000000..7e9b51cdb
--- /dev/null
+++ b/src/decoder/MpcdecDecoderPlugin.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_DECODER_MPCDEC_HXX
+#define MPD_DECODER_MPCDEC_HXX
+
+extern const struct decoder_plugin mpcdec_decoder_plugin;
+
+#endif
diff --git a/src/decoder/Mpg123DecoderPlugin.cxx b/src/decoder/Mpg123DecoderPlugin.cxx
new file mode 100644
index 000000000..05fe4717c
--- /dev/null
+++ b/src/decoder/Mpg123DecoderPlugin.cxx
@@ -0,0 +1,250 @@
+/*
+ * 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" /* must be first for large file support */
+#include "Mpg123DecoderPlugin.hxx"
+#include "DecoderAPI.hxx"
+#include "CheckAudioFormat.hxx"
+#include "TagHandler.hxx"
+
+#include <glib.h>
+
+#include <mpg123.h>
+#include <stdio.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "mpg123"
+
+static bool
+mpd_mpg123_init(gcc_unused const config_param &param)
+{
+ mpg123_init();
+
+ return true;
+}
+
+static void
+mpd_mpg123_finish(void)
+{
+ mpg123_exit();
+}
+
+/**
+ * Opens a file with an existing #mpg123_handle.
+ *
+ * @param handle a handle which was created before; on error, this
+ * function will not free it
+ * @param audio_format this parameter is filled after successful
+ * return
+ * @return true on success
+ */
+static bool
+mpd_mpg123_open(mpg123_handle *handle, const char *path_fs,
+ AudioFormat &audio_format)
+{
+ GError *gerror = nullptr;
+ char *path_dup;
+ int error;
+ int channels, encoding;
+ long rate;
+
+ /* mpg123_open() wants a writable string :-( */
+ path_dup = g_strdup(path_fs);
+
+ error = mpg123_open(handle, path_dup);
+ g_free(path_dup);
+ if (error != MPG123_OK) {
+ g_warning("libmpg123 failed to open %s: %s",
+ path_fs, mpg123_plain_strerror(error));
+ return false;
+ }
+
+ /* obtain the audio format */
+
+ error = mpg123_getformat(handle, &rate, &channels, &encoding);
+ if (error != MPG123_OK) {
+ g_warning("mpg123_getformat() failed: %s",
+ mpg123_plain_strerror(error));
+ return false;
+ }
+
+ if (encoding != MPG123_ENC_SIGNED_16) {
+ /* other formats not yet implemented */
+ g_warning("expected MPG123_ENC_SIGNED_16, got %d", encoding);
+ return false;
+ }
+
+ if (!audio_format_init_checked(audio_format, rate, SampleFormat::S16,
+ channels, &gerror)) {
+ g_warning("%s", gerror->message);
+ g_error_free(gerror);
+ return false;
+ }
+
+ return true;
+}
+
+static void
+mpd_mpg123_file_decode(struct decoder *decoder, const char *path_fs)
+{
+ mpg123_handle *handle;
+ int error;
+ off_t num_samples;
+ enum decoder_command cmd;
+ struct mpg123_frameinfo info;
+
+ /* open the file */
+
+ handle = mpg123_new(nullptr, &error);
+ if (handle == nullptr) {
+ g_warning("mpg123_new() failed: %s",
+ mpg123_plain_strerror(error));
+ return;
+ }
+
+ AudioFormat audio_format;
+ if (!mpd_mpg123_open(handle, path_fs, audio_format)) {
+ mpg123_delete(handle);
+ return;
+ }
+
+ num_samples = mpg123_length(handle);
+
+ /* tell MPD core we're ready */
+
+ decoder_initialized(decoder, audio_format, true,
+ (float)num_samples /
+ (float)audio_format.sample_rate);
+
+ if (mpg123_info(handle, &info) != MPG123_OK) {
+ info.vbr = MPG123_CBR;
+ info.bitrate = 0;
+ }
+
+ switch (info.vbr) {
+ case MPG123_ABR:
+ info.bitrate = info.abr_rate;
+ break;
+ case MPG123_CBR:
+ break;
+ default:
+ info.bitrate = 0;
+ }
+
+ /* the decoder main loop */
+
+ do {
+ unsigned char buffer[8192];
+ size_t nbytes;
+
+ /* decode */
+
+ error = mpg123_read(handle, buffer, sizeof(buffer), &nbytes);
+ if (error != MPG123_OK) {
+ if (error != MPG123_DONE)
+ g_warning("mpg123_read() failed: %s",
+ mpg123_plain_strerror(error));
+ break;
+ }
+
+ /* update bitrate for ABR/VBR */
+ if (info.vbr != MPG123_CBR) {
+ /* FIXME: maybe skip, as too expensive? */
+ /* FIXME: maybe, (info.vbr == MPG123_VBR) ? */
+ if (mpg123_info (handle, &info) != MPG123_OK)
+ info.bitrate = 0;
+ }
+
+ /* send to MPD */
+
+ cmd = decoder_data(decoder, nullptr, buffer, nbytes, info.bitrate);
+
+ if (cmd == DECODE_COMMAND_SEEK) {
+ off_t c = decoder_seek_where(decoder)*audio_format.sample_rate;
+ c = mpg123_seek(handle, c, SEEK_SET);
+ if (c < 0)
+ decoder_seek_error(decoder);
+ else {
+ decoder_command_finished(decoder);
+ decoder_timestamp(decoder, c/(double)audio_format.sample_rate);
+ }
+
+ cmd = DECODE_COMMAND_NONE;
+ }
+ } while (cmd == DECODE_COMMAND_NONE);
+
+ /* cleanup */
+
+ mpg123_delete(handle);
+}
+
+static bool
+mpd_mpg123_scan_file(const char *path_fs,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ mpg123_handle *handle;
+ int error;
+ off_t num_samples;
+
+ handle = mpg123_new(nullptr, &error);
+ if (handle == nullptr) {
+ g_warning("mpg123_new() failed: %s",
+ mpg123_plain_strerror(error));
+ return false;
+ }
+
+ AudioFormat audio_format;
+ if (!mpd_mpg123_open(handle, path_fs, audio_format)) {
+ mpg123_delete(handle);
+ return false;
+ }
+
+ num_samples = mpg123_length(handle);
+ if (num_samples <= 0) {
+ mpg123_delete(handle);
+ return false;
+ }
+
+ /* ID3 tag support not yet implemented */
+
+ mpg123_delete(handle);
+
+ tag_handler_invoke_duration(handler, handler_ctx,
+ num_samples / audio_format.sample_rate);
+ return true;
+}
+
+static const char *const mpg123_suffixes[] = {
+ "mp3",
+ nullptr
+};
+
+const struct decoder_plugin mpg123_decoder_plugin = {
+ "mpg123",
+ mpd_mpg123_init,
+ mpd_mpg123_finish,
+ /* streaming not yet implemented */
+ nullptr,
+ mpd_mpg123_file_decode,
+ mpd_mpg123_scan_file,
+ nullptr,
+ nullptr,
+ mpg123_suffixes,
+ nullptr,
+};
diff --git a/src/decoder/Mpg123DecoderPlugin.hxx b/src/decoder/Mpg123DecoderPlugin.hxx
new file mode 100644
index 000000000..273b03eaf
--- /dev/null
+++ b/src/decoder/Mpg123DecoderPlugin.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_DECODER_MPG123_HXX
+#define MPD_DECODER_MPG123_HXX
+
+extern const struct decoder_plugin mpg123_decoder_plugin;
+
+#endif
diff --git a/src/decoder/OggCodec.cxx b/src/decoder/OggCodec.cxx
new file mode 100644
index 000000000..d7e5b7642
--- /dev/null
+++ b/src/decoder/OggCodec.cxx
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+/*
+ * Common functions used for Ogg data streams (Ogg-Vorbis and OggFLAC)
+ */
+
+#include "config.h"
+#include "OggCodec.hxx"
+
+#include <string.h>
+
+enum ogg_codec
+ogg_codec_detect(struct decoder *decoder, struct input_stream *is)
+{
+ /* oggflac detection based on code in ogg123 and this post
+ * http://lists.xiph.org/pipermail/flac/2004-December/000393.html
+ * ogg123 trunk still doesn't have this patch as of June 2005 */
+ unsigned char buf[41];
+ size_t r = decoder_read(decoder, is, buf, sizeof(buf));
+ if (r < sizeof(buf) || memcmp(buf, "OggS", 4) != 0)
+ return OGG_CODEC_UNKNOWN;
+
+ if ((memcmp(buf + 29, "FLAC", 4) == 0 &&
+ memcmp(buf + 37, "fLaC", 4) == 0) ||
+ memcmp(buf + 28, "FLAC", 4) == 0 ||
+ memcmp(buf + 28, "fLaC", 4) == 0)
+ return OGG_CODEC_FLAC;
+
+ if (memcmp(buf + 28, "Opus", 4) == 0)
+ return OGG_CODEC_OPUS;
+
+ return OGG_CODEC_VORBIS;
+}
diff --git a/src/decoder/OggCodec.hxx b/src/decoder/OggCodec.hxx
new file mode 100644
index 000000000..eb709286b
--- /dev/null
+++ b/src/decoder/OggCodec.hxx
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+/*
+ * Common functions used for Ogg data streams (Ogg-Vorbis and OggFLAC)
+ */
+
+#ifndef MPD_OGG_CODEC_HXX
+#define MPD_OGG_CODEC_HXX
+
+#include "DecoderAPI.hxx"
+
+enum ogg_codec {
+ OGG_CODEC_UNKNOWN,
+ OGG_CODEC_VORBIS,
+ OGG_CODEC_FLAC,
+ OGG_CODEC_OPUS,
+};
+
+enum ogg_codec
+ogg_codec_detect(struct decoder *decoder, struct input_stream *is);
+
+#endif /* _OGG_COMMON_H */
diff --git a/src/decoder/OggFind.cxx b/src/decoder/OggFind.cxx
new file mode 100644
index 000000000..9df4c6455
--- /dev/null
+++ b/src/decoder/OggFind.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 "config.h"
+#include "OggFind.hxx"
+#include "OggSyncState.hxx"
+
+bool
+OggFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet)
+{
+ while (true) {
+ int r = ogg_stream_packetout(&os, &packet);
+ if (r == 0) {
+ if (!oy.ExpectPageIn(os))
+ return false;
+
+ continue;
+ } else if (r > 0 && packet.e_o_s)
+ return true;
+ }
+}
diff --git a/src/decoder/OggFind.hxx b/src/decoder/OggFind.hxx
new file mode 100644
index 000000000..7d18d2067
--- /dev/null
+++ b/src/decoder/OggFind.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_OGG_FIND_HXX
+#define MPD_OGG_FIND_HXX
+
+#include "check.h"
+
+#include <ogg/ogg.h>
+
+class OggSyncState;
+
+/**
+ * Skip all pages/packets until an end-of-stream (EOS) packet for the
+ * specified stream is found.
+ *
+ * @return true if the EOS packet was found
+ */
+bool
+OggFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet);
+
+#endif
diff --git a/src/decoder/OggSyncState.hxx b/src/decoder/OggSyncState.hxx
new file mode 100644
index 000000000..eaeb9bd8c
--- /dev/null
+++ b/src/decoder/OggSyncState.hxx
@@ -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.
+ */
+
+#ifndef MPD_OGG_SYNC_STATE_HXX
+#define MPD_OGG_SYNC_STATE_HXX
+
+#include "check.h"
+#include "OggUtil.hxx"
+
+#include <ogg/ogg.h>
+
+#include <stddef.h>
+
+/**
+ * Wrapper for an ogg_sync_state.
+ */
+class OggSyncState {
+ ogg_sync_state oy;
+
+ input_stream &is;
+ struct decoder *const decoder;
+
+public:
+ OggSyncState(input_stream &_is, struct decoder *const _decoder=nullptr)
+ :is(_is), decoder(_decoder) {
+ ogg_sync_init(&oy);
+ }
+
+ ~OggSyncState() {
+ ogg_sync_clear(&oy);
+ }
+
+ void Reset() {
+ ogg_sync_reset(&oy);
+ }
+
+ bool Feed(size_t size) {
+ return OggFeed(oy, decoder, &is, size);
+ }
+
+ bool ExpectPage(ogg_page &page) {
+ return OggExpectPage(oy, page, decoder, &is);
+ }
+
+ bool ExpectFirstPage(ogg_stream_state &os) {
+ return OggExpectFirstPage(oy, os, decoder, &is);
+ }
+
+ bool ExpectPageIn(ogg_stream_state &os) {
+ return OggExpectPageIn(oy, os, decoder, &is);
+ }
+
+ bool ExpectPageSeek(ogg_page &page) {
+ return OggExpectPageSeek(oy, page, decoder, &is);
+ }
+
+ bool ExpectPageSeekIn(ogg_stream_state &os) {
+ return OggExpectPageSeekIn(oy, os, decoder, &is);
+ }
+};
+
+#endif
diff --git a/src/decoder/OggUtil.cxx b/src/decoder/OggUtil.cxx
new file mode 100644
index 000000000..0e2f48f51
--- /dev/null
+++ b/src/decoder/OggUtil.cxx
@@ -0,0 +1,118 @@
+/*
+ * 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 "OggUtil.hxx"
+#include "DecoderAPI.hxx"
+
+bool
+OggFeed(ogg_sync_state &oy, struct decoder *decoder,
+ input_stream *input_stream, size_t size)
+{
+ char *buffer = ogg_sync_buffer(&oy, size);
+ if (buffer == nullptr)
+ return false;
+
+ size_t nbytes = decoder_read(decoder, input_stream,
+ buffer, size);
+ if (nbytes == 0)
+ return false;
+
+ ogg_sync_wrote(&oy, nbytes);
+ return true;
+}
+
+bool
+OggExpectPage(ogg_sync_state &oy, ogg_page &page,
+ decoder *decoder, input_stream *input_stream)
+{
+ while (true) {
+ int r = ogg_sync_pageout(&oy, &page);
+ if (r != 0)
+ return r > 0;
+
+ if (!OggFeed(oy, decoder, input_stream, 1024))
+ return false;
+ }
+}
+
+bool
+OggExpectFirstPage(ogg_sync_state &oy, ogg_stream_state &os,
+ decoder *decoder, input_stream *is)
+{
+ ogg_page page;
+ if (!OggExpectPage(oy, page, decoder, is))
+ return false;
+
+ ogg_stream_init(&os, ogg_page_serialno(&page));
+ ogg_stream_pagein(&os, &page);
+ return true;
+}
+
+bool
+OggExpectPageIn(ogg_sync_state &oy, ogg_stream_state &os,
+ decoder *decoder, input_stream *is)
+{
+ ogg_page page;
+ if (!OggExpectPage(oy, page, decoder, is))
+ return false;
+
+ ogg_stream_pagein(&os, &page);
+ return true;
+}
+
+bool
+OggExpectPageSeek(ogg_sync_state &oy, ogg_page &page,
+ decoder *decoder, input_stream *input_stream)
+{
+ size_t remaining_skipped = 16384;
+
+ while (true) {
+ int r = ogg_sync_pageseek(&oy, &page);
+ if (r > 0)
+ return true;
+
+ if (r < 0) {
+ /* skipped -r bytes */
+ size_t nbytes = -r;
+ if (nbytes > remaining_skipped)
+ /* still no ogg page - we lost our
+ patience, abort */
+ return false;
+
+ remaining_skipped -= nbytes;
+ continue;
+ }
+
+ if (!OggFeed(oy, decoder, input_stream, 1024))
+ return false;
+ }
+}
+
+bool
+OggExpectPageSeekIn(ogg_sync_state &oy, ogg_stream_state &os,
+ decoder *decoder, input_stream *is)
+{
+ ogg_page page;
+ if (!OggExpectPageSeek(oy, page, decoder, is))
+ return false;
+
+ ogg_stream_pagein(&os, &page);
+ return true;
+}
diff --git a/src/decoder/OggUtil.hxx b/src/decoder/OggUtil.hxx
new file mode 100644
index 000000000..324797815
--- /dev/null
+++ b/src/decoder/OggUtil.hxx
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ */
+
+#ifndef MPD_OGG_UTIL_HXX
+#define MPD_OGG_UTIL_HXX
+
+#include "check.h"
+
+#include <ogg/ogg.h>
+
+#include <stddef.h>
+
+struct input_stream;
+struct decoder;
+
+/**
+ * Feed data from the #input_stream into the #ogg_sync_state.
+ *
+ * @return false on error or end-of-file
+ */
+bool
+OggFeed(ogg_sync_state &oy, struct decoder *decoder, input_stream *is,
+ size_t size);
+
+/**
+ * Feed into the #ogg_sync_state until a page gets available. Garbage
+ * data at the beginning is considered a fatal error.
+ *
+ * @return true if a page is available
+ */
+bool
+OggExpectPage(ogg_sync_state &oy, ogg_page &page,
+ decoder *decoder, input_stream *input_stream);
+
+/**
+ * Combines OggExpectPage(), ogg_stream_init() and
+ * ogg_stream_pagein().
+ *
+ * @return true if the stream was initialized and the first page was
+ * delivered to it
+ */
+bool
+OggExpectFirstPage(ogg_sync_state &oy, ogg_stream_state &os,
+ decoder *decoder, input_stream *is);
+
+/**
+ * Combines OggExpectPage() and ogg_stream_pagein().
+ *
+ * @return true if a page was delivered to the stream
+ */
+bool
+OggExpectPageIn(ogg_sync_state &oy, ogg_stream_state &os,
+ decoder *decoder, input_stream *is);
+
+/**
+ * Like OggExpectPage(), but allow skipping garbage (after seeking).
+ */
+bool
+OggExpectPageSeek(ogg_sync_state &oy, ogg_page &page,
+ decoder *decoder, input_stream *input_stream);
+
+/**
+ * Combines OggExpectPageSeek() and ogg_stream_pagein().
+ *
+ * @return true if a page was delivered to the stream
+ */
+bool
+OggExpectPageSeekIn(ogg_sync_state &oy, ogg_stream_state &os,
+ decoder *decoder, input_stream *is);
+
+#endif
diff --git a/src/decoder/OpusDecoderPlugin.cxx b/src/decoder/OpusDecoderPlugin.cxx
new file mode 100644
index 000000000..b6835f760
--- /dev/null
+++ b/src/decoder/OpusDecoderPlugin.cxx
@@ -0,0 +1,400 @@
+/*
+ * 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" /* must be first for large file support */
+#include "OpusDecoderPlugin.h"
+#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 "TagHandler.hxx"
+#include "InputStream.hxx"
+
+#include <opus.h>
+#include <ogg/ogg.h>
+
+#include <glib.h>
+
+#include <stdio.h>
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "opus"
+
+static const opus_int32 opus_sample_rate = 48000;
+
+gcc_pure
+static bool
+IsOpusHead(const ogg_packet &packet)
+{
+ return packet.bytes >= 8 && memcmp(packet.packet, "OpusHead", 8) == 0;
+}
+
+gcc_pure
+static bool
+IsOpusTags(const ogg_packet &packet)
+{
+ return packet.bytes >= 8 && memcmp(packet.packet, "OpusTags", 8) == 0;
+}
+
+static bool
+mpd_opus_init(gcc_unused const config_param &param)
+{
+ g_debug("%s", opus_get_version_string());
+
+ return true;
+}
+
+class MPDOpusDecoder {
+ struct decoder *decoder;
+ struct input_stream *input_stream;
+
+ ogg_stream_state os;
+
+ OpusDecoder *opus_decoder;
+ opus_int16 *output_buffer;
+ unsigned output_size;
+
+ bool os_initialized;
+ bool found_opus;
+
+ int opus_serialno;
+
+ size_t frame_size;
+
+public:
+ MPDOpusDecoder(struct decoder *_decoder,
+ struct input_stream *_input_stream)
+ :decoder(_decoder), input_stream(_input_stream),
+ opus_decoder(nullptr),
+ output_buffer(nullptr), output_size(0),
+ os_initialized(false), found_opus(false) {}
+ ~MPDOpusDecoder();
+
+ bool ReadFirstPage(OggSyncState &oy);
+ bool ReadNextPage(OggSyncState &oy);
+
+ enum decoder_command HandlePackets();
+ enum decoder_command HandlePacket(const ogg_packet &packet);
+ enum decoder_command HandleBOS(const ogg_packet &packet);
+ enum decoder_command HandleTags(const ogg_packet &packet);
+ enum decoder_command HandleAudio(const ogg_packet &packet);
+};
+
+MPDOpusDecoder::~MPDOpusDecoder()
+{
+ g_free(output_buffer);
+
+ if (opus_decoder != nullptr)
+ opus_decoder_destroy(opus_decoder);
+
+ if (os_initialized)
+ ogg_stream_clear(&os);
+}
+
+inline bool
+MPDOpusDecoder::ReadFirstPage(OggSyncState &oy)
+{
+ assert(!os_initialized);
+
+ if (!oy.ExpectFirstPage(os))
+ return false;
+
+ os_initialized = true;
+ return true;
+}
+
+inline bool
+MPDOpusDecoder::ReadNextPage(OggSyncState &oy)
+{
+ assert(os_initialized);
+
+ ogg_page page;
+ if (!oy.ExpectPage(page))
+ return false;
+
+ const auto page_serialno = ogg_page_serialno(&page);
+ if (page_serialno != os.serialno)
+ ogg_stream_reset_serialno(&os, page_serialno);
+
+ ogg_stream_pagein(&os, &page);
+ return true;
+}
+
+inline enum decoder_command
+MPDOpusDecoder::HandlePackets()
+{
+ ogg_packet packet;
+ while (ogg_stream_packetout(&os, &packet) == 1) {
+ enum decoder_command cmd = HandlePacket(packet);
+ if (cmd != DECODE_COMMAND_NONE)
+ return cmd;
+ }
+
+ return DECODE_COMMAND_NONE;
+}
+
+inline enum decoder_command
+MPDOpusDecoder::HandlePacket(const ogg_packet &packet)
+{
+ if (packet.e_o_s)
+ return DECODE_COMMAND_STOP;
+
+ if (packet.b_o_s)
+ return HandleBOS(packet);
+ else if (!found_opus)
+ return DECODE_COMMAND_STOP;
+
+ if (IsOpusTags(packet))
+ return HandleTags(packet);
+
+ return HandleAudio(packet);
+}
+
+inline enum decoder_command
+MPDOpusDecoder::HandleBOS(const ogg_packet &packet)
+{
+ assert(packet.b_o_s);
+
+ if (found_opus || !IsOpusHead(packet))
+ return DECODE_COMMAND_STOP;
+
+ unsigned channels;
+ if (!ScanOpusHeader(packet.packet, packet.bytes, channels) ||
+ !audio_valid_channel_count(channels))
+ return DECODE_COMMAND_STOP;
+
+ assert(opus_decoder == nullptr);
+ assert(output_buffer == nullptr);
+
+ opus_serialno = os.serialno;
+ found_opus = true;
+
+ /* TODO: parse attributes from the OpusHead (sample rate,
+ channels, ...) */
+
+ int opus_error;
+ opus_decoder = opus_decoder_create(opus_sample_rate, channels,
+ &opus_error);
+ if (opus_decoder == nullptr) {
+ g_warning("libopus error: %s",
+ opus_strerror(opus_error));
+ return DECODE_COMMAND_STOP;
+ }
+
+ const AudioFormat audio_format(opus_sample_rate,
+ SampleFormat::S16, channels);
+ decoder_initialized(decoder, audio_format, false, -1);
+ frame_size = audio_format.GetFrameSize();
+
+ /* allocate an output buffer for 16 bit PCM samples big enough
+ to hold a quarter second, larger than 120ms required by
+ libopus */
+ output_size = audio_format.sample_rate / 4;
+ output_buffer = (opus_int16 *)
+ g_malloc(sizeof(*output_buffer) * output_size *
+ audio_format.channels);
+
+ return decoder_get_command(decoder);
+}
+
+inline enum decoder_command
+MPDOpusDecoder::HandleTags(const ogg_packet &packet)
+{
+ Tag tag;
+
+ enum decoder_command cmd;
+ if (ScanOpusTags(packet.packet, packet.bytes,
+ &add_tag_handler, &tag) &&
+ !tag.IsEmpty())
+ cmd = decoder_tag(decoder, input_stream, std::move(tag));
+ else
+ cmd = decoder_get_command(decoder);
+
+ return cmd;
+}
+
+inline enum decoder_command
+MPDOpusDecoder::HandleAudio(const ogg_packet &packet)
+{
+ assert(opus_decoder != nullptr);
+
+ int nframes = opus_decode(opus_decoder,
+ (const unsigned char*)packet.packet,
+ packet.bytes,
+ output_buffer, output_size,
+ 0);
+ if (nframes < 0) {
+ g_warning("%s", opus_strerror(nframes));
+ return DECODE_COMMAND_STOP;
+ }
+
+ if (nframes > 0) {
+ const size_t nbytes = nframes * frame_size;
+ enum decoder_command cmd =
+ decoder_data(decoder, input_stream,
+ output_buffer, nbytes,
+ 0);
+ if (cmd != DECODE_COMMAND_NONE)
+ return cmd;
+ }
+
+ return DECODE_COMMAND_NONE;
+}
+
+static void
+mpd_opus_stream_decode(struct decoder *decoder,
+ struct input_stream *input_stream)
+{
+ if (ogg_codec_detect(decoder, input_stream) != OGG_CODEC_OPUS)
+ return;
+
+ /* rewind the stream, because ogg_codec_detect() has
+ moved it */
+ input_stream_lock_seek(input_stream, 0, SEEK_SET, nullptr);
+
+ MPDOpusDecoder d(decoder, input_stream);
+ OggSyncState oy(*input_stream, decoder);
+
+ if (!d.ReadFirstPage(oy))
+ return;
+
+ while (true) {
+ enum decoder_command cmd = d.HandlePackets();
+ if (cmd != DECODE_COMMAND_NONE)
+ break;
+
+ if (!d.ReadNextPage(oy))
+ break;
+
+ }
+}
+
+static bool
+SeekFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet,
+ input_stream *is)
+{
+ if (is->size > 0 && is->size - is->offset < 65536)
+ return OggFindEOS(oy, os, packet);
+
+ if (!input_stream_cheap_seeking(is))
+ return false;
+
+ oy.Reset();
+
+ return input_stream_lock_seek(is, -65536, SEEK_END, nullptr) &&
+ oy.ExpectPageSeekIn(os) &&
+ OggFindEOS(oy, os, packet);
+}
+
+static bool
+mpd_opus_scan_stream(struct input_stream *is,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ OggSyncState oy(*is);
+
+ ogg_stream_state os;
+ if (!oy.ExpectFirstPage(os))
+ return false;
+
+ /* read at most two more pages */
+ unsigned remaining_pages = 2;
+
+ bool result = false;
+
+ ogg_packet packet;
+ while (true) {
+ int r = ogg_stream_packetout(&os, &packet);
+ if (r < 0) {
+ result = false;
+ break;
+ }
+
+ if (r == 0) {
+ if (remaining_pages-- == 0)
+ break;
+
+ if (!oy.ExpectPageIn(os)) {
+ result = false;
+ break;
+ }
+
+ continue;
+ }
+
+ if (packet.b_o_s) {
+ if (!IsOpusHead(packet))
+ break;
+
+ unsigned channels;
+ if (!ScanOpusHeader(packet.packet, packet.bytes, channels) ||
+ !audio_valid_channel_count(channels)) {
+ result = false;
+ break;
+ }
+
+ result = true;
+ } else if (!result)
+ break;
+ else if (IsOpusTags(packet)) {
+ if (!ScanOpusTags(packet.packet, packet.bytes,
+ handler, handler_ctx))
+ result = false;
+
+ break;
+ }
+ }
+
+ if (packet.e_o_s || SeekFindEOS(oy, os, packet, is))
+ tag_handler_invoke_duration(handler, handler_ctx,
+ packet.granulepos / opus_sample_rate);
+
+ ogg_stream_clear(&os);
+
+ return result;
+}
+
+static const char *const opus_suffixes[] = {
+ "opus",
+ "ogg",
+ "oga",
+ nullptr
+};
+
+static const char *const opus_mime_types[] = {
+ "audio/opus",
+ nullptr
+};
+
+const struct decoder_plugin opus_decoder_plugin = {
+ "opus",
+ mpd_opus_init,
+ nullptr,
+ mpd_opus_stream_decode,
+ nullptr,
+ nullptr,
+ mpd_opus_scan_stream,
+ nullptr,
+ opus_suffixes,
+ opus_mime_types,
+};
diff --git a/src/decoder/OpusDecoderPlugin.h b/src/decoder/OpusDecoderPlugin.h
new file mode 100644
index 000000000..c95d6ded3
--- /dev/null
+++ b/src/decoder/OpusDecoderPlugin.h
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+#ifndef MPD_DECODER_OPUS_H
+#define MPD_DECODER_OPUS_H
+
+extern const struct decoder_plugin opus_decoder_plugin;
+
+#endif
diff --git a/src/decoder/OpusHead.cxx b/src/decoder/OpusHead.cxx
new file mode 100644
index 000000000..c57e08e10
--- /dev/null
+++ b/src/decoder/OpusHead.cxx
@@ -0,0 +1,44 @@
+/*
+ * 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 "OpusHead.hxx"
+
+#include <stdint.h>
+#include <string.h>
+
+struct OpusHead {
+ char signature[8];
+ uint8_t version, channels;
+ uint16_t pre_skip;
+ uint32_t sample_rate;
+ uint16_t output_gain;
+ uint8_t channel_mapping;
+};
+
+bool
+ScanOpusHeader(const void *data, size_t size, unsigned &channels_r)
+{
+ const OpusHead *h = (const OpusHead *)data;
+ if (size < 19 || (h->version & 0xf0) != 0)
+ return false;
+
+ channels_r = h->channels;
+ return true;
+}
diff --git a/src/decoder/OpusHead.hxx b/src/decoder/OpusHead.hxx
new file mode 100644
index 000000000..9f75c4f70
--- /dev/null
+++ b/src/decoder/OpusHead.hxx
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+#ifndef MPD_OPUS_HEAD_HXX
+#define MPD_OPUS_HEAD_HXX
+
+#include "check.h"
+
+#include <stddef.h>
+
+bool
+ScanOpusHeader(const void *data, size_t size, unsigned &channels_r);
+
+#endif
diff --git a/src/decoder/OpusReader.hxx b/src/decoder/OpusReader.hxx
new file mode 100644
index 000000000..7e161fd0f
--- /dev/null
+++ b/src/decoder/OpusReader.hxx
@@ -0,0 +1,100 @@
+/*
+ * 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.
+ */
+
+#ifndef MPD_OPUS_READER_HXX
+#define MPD_OPUS_READER_HXX
+
+#include "check.h"
+
+#include <stdint.h>
+#include <string.h>
+
+class OpusReader {
+ const uint8_t *p, *const end;
+
+public:
+ OpusReader(const void *_p, size_t size)
+ :p((const uint8_t *)_p), end(p + size) {}
+
+ bool Skip(size_t length) {
+ p += length;
+ return p <= end;
+ }
+
+ const void *Read(size_t length) {
+ const uint8_t *result = p;
+ return Skip(length)
+ ? result
+ : nullptr;
+ }
+
+ bool Expect(const void *value, size_t length) {
+ const void *data = Read(length);
+ return data != nullptr && memcmp(value, data, length) == 0;
+ }
+
+ bool ReadByte(uint8_t &value_r) {
+ if (p >= end)
+ return false;
+
+ value_r = *p++;
+ return true;
+ }
+
+ bool ReadShort(uint16_t &value_r) {
+ const uint8_t *value = (const uint8_t *)Read(sizeof(value_r));
+ if (value == nullptr)
+ return false;
+
+ value_r = value[0] | (value[1] << 8);
+ return true;
+ }
+
+ bool ReadWord(uint32_t &value_r) {
+ const uint8_t *value = (const uint8_t *)Read(sizeof(value_r));
+ if (value == nullptr)
+ return false;
+
+ value_r = value[0] | (value[1] << 8)
+ | (value[2] << 16) | (value[3] << 24);
+ return true;
+ }
+
+ bool SkipString() {
+ uint32_t length;
+ return ReadWord(length) && Skip(length);
+ }
+
+ char *ReadString() {
+ uint32_t length;
+ if (!ReadWord(length))
+ return nullptr;
+
+ const char *src = (const char *)Read(length);
+ if (src == nullptr)
+ return nullptr;
+
+ char *dest = new char[length + 1];
+ memcpy(dest, src, length);
+ dest[length] = 0;
+ return dest;
+ }
+};
+
+#endif
diff --git a/src/decoder/OpusTags.cxx b/src/decoder/OpusTags.cxx
new file mode 100644
index 000000000..a63dc1c24
--- /dev/null
+++ b/src/decoder/OpusTags.cxx
@@ -0,0 +1,77 @@
+/*
+ * 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 "OpusTags.hxx"
+#include "OpusReader.hxx"
+#include "XiphTags.hxx"
+#include "TagHandler.hxx"
+
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+
+static void
+ScanOneOpusTag(const char *name, const char *value,
+ const struct tag_handler *handler, void *ctx)
+{
+ tag_handler_invoke_pair(handler, ctx, name, value);
+
+ if (handler->tag != nullptr) {
+ enum tag_type t = tag_table_lookup_i(xiph_tags, name);
+ if (t != TAG_NUM_OF_ITEM_TYPES)
+ tag_handler_invoke_tag(handler, ctx, t, value);
+ }
+}
+
+bool
+ScanOpusTags(const void *data, size_t size,
+ const struct tag_handler *handler, void *ctx)
+{
+ OpusReader r(data, size);
+ if (!r.Expect("OpusTags", 8))
+ return false;
+
+ if (handler->pair == nullptr && handler->tag == nullptr)
+ return true;
+
+ if (!r.SkipString())
+ return false;
+
+ uint32_t n;
+ if (!r.ReadWord(n))
+ return false;
+
+ while (n-- > 0) {
+ char *p = r.ReadString();
+ if (p == nullptr)
+ return false;
+
+ char *eq = strchr(p, '=');
+ if (eq != nullptr && eq > p) {
+ *eq = 0;
+
+ ScanOneOpusTag(p, eq + 1, handler, ctx);
+ }
+
+ free(p);
+ }
+
+ return true;
+}
diff --git a/src/decoder/OpusTags.hxx b/src/decoder/OpusTags.hxx
new file mode 100644
index 000000000..2f3eec844
--- /dev/null
+++ b/src/decoder/OpusTags.hxx
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+#ifndef MPD_OPUS_TAGS_HXX
+#define MPD_OPUS_TAGS_HXX
+
+#include "check.h"
+
+#include <stddef.h>
+
+bool
+ScanOpusTags(const void *data, size_t size,
+ const struct tag_handler *handler, void *ctx);
+
+#endif
diff --git a/src/decoder/PcmDecoderPlugin.cxx b/src/decoder/PcmDecoderPlugin.cxx
new file mode 100644
index 000000000..8976f511f
--- /dev/null
+++ b/src/decoder/PcmDecoderPlugin.cxx
@@ -0,0 +1,119 @@
+/*
+ * 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 "decoder/PcmDecoderPlugin.hxx"
+#include "DecoderAPI.hxx"
+
+extern "C" {
+#include "util/byte_reverse.h"
+}
+
+#include <glib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdio.h> /* for SEEK_SET */
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "pcm"
+
+static void
+pcm_stream_decode(struct decoder *decoder, struct input_stream *is)
+{
+ static constexpr AudioFormat audio_format = {
+ 44100,
+ SampleFormat::S16,
+ 2,
+ };
+
+ const char *const mime = input_stream_get_mime_type(is);
+ const bool reverse_endian = mime != nullptr &&
+ strcmp(mime, "audio/x-mpd-cdda-pcm-reverse") == 0;
+
+ GError *error = nullptr;
+ enum decoder_command cmd;
+
+ const double time_to_size = audio_format.GetTimeToSize();
+
+ float total_time = -1;
+ const goffset size = input_stream_get_size(is);
+ if (size >= 0)
+ total_time = size / time_to_size;
+
+ decoder_initialized(decoder, audio_format,
+ input_stream_is_seekable(is), total_time);
+
+ do {
+ char buffer[4096];
+
+ size_t nbytes = decoder_read(decoder, is,
+ buffer, sizeof(buffer));
+
+ if (nbytes == 0 && input_stream_lock_eof(is))
+ break;
+
+ if (reverse_endian)
+ /* make sure we deliver samples in host byte order */
+ reverse_bytes_16((uint16_t *)buffer,
+ (uint16_t *)buffer,
+ (uint16_t *)(buffer + nbytes));
+
+ cmd = nbytes > 0
+ ? decoder_data(decoder, is,
+ buffer, nbytes, 0)
+ : decoder_get_command(decoder);
+ if (cmd == DECODE_COMMAND_SEEK) {
+ goffset offset = (goffset)(time_to_size *
+ decoder_seek_where(decoder));
+ if (input_stream_lock_seek(is, offset, SEEK_SET,
+ &error)) {
+ decoder_command_finished(decoder);
+ } else {
+ g_warning("seeking failed: %s", error->message);
+ g_error_free(error);
+ decoder_seek_error(decoder);
+ }
+
+ cmd = DECODE_COMMAND_NONE;
+ }
+ } while (cmd == DECODE_COMMAND_NONE);
+}
+
+static const char *const pcm_mime_types[] = {
+ /* for streams obtained by the cdio_paranoia input plugin */
+ "audio/x-mpd-cdda-pcm",
+
+ /* same as above, but with reverse byte order */
+ "audio/x-mpd-cdda-pcm-reverse",
+
+ nullptr
+};
+
+const struct decoder_plugin pcm_decoder_plugin = {
+ "pcm",
+ nullptr,
+ nullptr,
+ pcm_stream_decode,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ pcm_mime_types,
+};
diff --git a/src/decoder/PcmDecoderPlugin.hxx b/src/decoder/PcmDecoderPlugin.hxx
new file mode 100644
index 000000000..2883e866e
--- /dev/null
+++ b/src/decoder/PcmDecoderPlugin.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.
+ */
+
+/** \file
+ *
+ * Not really a decoder; this plugin forwards its input data "as-is".
+ *
+ * It was written only to support the "cdio_paranoia" input plugin,
+ * which does not need a decoder.
+ */
+
+#ifndef MPD_DECODER_PCM_HXX
+#define MPD_DECODER_PCM_HXX
+
+extern const struct decoder_plugin pcm_decoder_plugin;
+
+#endif
diff --git a/src/decoder/SndfileDecoderPlugin.cxx b/src/decoder/SndfileDecoderPlugin.cxx
new file mode 100644
index 000000000..63401a47b
--- /dev/null
+++ b/src/decoder/SndfileDecoderPlugin.cxx
@@ -0,0 +1,261 @@
+/*
+ * 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 "SndfileDecoderPlugin.hxx"
+#include "DecoderAPI.hxx"
+#include "CheckAudioFormat.hxx"
+#include "TagHandler.hxx"
+
+#include <sndfile.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "sndfile"
+
+static sf_count_t
+sndfile_vio_get_filelen(void *user_data)
+{
+ const struct input_stream *is = (const struct input_stream *)user_data;
+
+ return input_stream_get_size(is);
+}
+
+static sf_count_t
+sndfile_vio_seek(sf_count_t offset, int whence, void *user_data)
+{
+ struct input_stream *is = (struct input_stream *)user_data;
+ bool success;
+
+ success = input_stream_lock_seek(is, offset, whence, nullptr);
+ if (!success)
+ return -1;
+
+ return input_stream_get_offset(is);
+}
+
+static sf_count_t
+sndfile_vio_read(void *ptr, sf_count_t count, void *user_data)
+{
+ struct input_stream *is = (struct input_stream *)user_data;
+ GError *error = nullptr;
+ size_t nbytes;
+
+ nbytes = input_stream_lock_read(is, ptr, count, &error);
+ if (nbytes == 0 && error != nullptr) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return -1;
+ }
+
+ return nbytes;
+}
+
+static sf_count_t
+sndfile_vio_write(G_GNUC_UNUSED const void *ptr,
+ G_GNUC_UNUSED sf_count_t count,
+ G_GNUC_UNUSED void *user_data)
+{
+ /* no writing! */
+ return -1;
+}
+
+static sf_count_t
+sndfile_vio_tell(void *user_data)
+{
+ const struct input_stream *is = (const struct input_stream *)user_data;
+
+ return input_stream_get_offset(is);
+}
+
+/**
+ * This SF_VIRTUAL_IO implementation wraps MPD's #input_stream to a
+ * libsndfile stream.
+ */
+static SF_VIRTUAL_IO vio = {
+ sndfile_vio_get_filelen,
+ sndfile_vio_seek,
+ sndfile_vio_read,
+ sndfile_vio_write,
+ sndfile_vio_tell,
+};
+
+/**
+ * Converts a frame number to a timestamp (in seconds).
+ */
+static float
+frame_to_time(sf_count_t frame, const AudioFormat *audio_format)
+{
+ return (float)frame / (float)audio_format->sample_rate;
+}
+
+/**
+ * Converts a timestamp (in seconds) to a frame number.
+ */
+static sf_count_t
+time_to_frame(float t, const AudioFormat *audio_format)
+{
+ return (sf_count_t)(t * audio_format->sample_rate);
+}
+
+static void
+sndfile_stream_decode(struct decoder *decoder, struct input_stream *is)
+{
+ GError *error = nullptr;
+ SNDFILE *sf;
+ SF_INFO info;
+ size_t frame_size;
+ sf_count_t read_frames, num_frames;
+ int buffer[4096];
+ enum decoder_command cmd;
+
+ info.format = 0;
+
+ sf = sf_open_virtual(&vio, SFM_READ, &info, is);
+ if (sf == nullptr) {
+ g_warning("sf_open_virtual() failed");
+ return;
+ }
+
+ /* for now, always read 32 bit samples. Later, we could lower
+ MPD's CPU usage by reading 16 bit samples with
+ sf_readf_short() on low-quality source files. */
+ AudioFormat audio_format;
+ if (!audio_format_init_checked(audio_format, info.samplerate,
+ SampleFormat::S32,
+ info.channels, &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return;
+ }
+
+ decoder_initialized(decoder, audio_format, info.seekable,
+ frame_to_time(info.frames, &audio_format));
+
+ frame_size = audio_format.GetFrameSize();
+ read_frames = sizeof(buffer) / frame_size;
+
+ do {
+ num_frames = sf_readf_int(sf, buffer, read_frames);
+ if (num_frames <= 0)
+ break;
+
+ cmd = decoder_data(decoder, is,
+ buffer, num_frames * frame_size,
+ 0);
+ if (cmd == DECODE_COMMAND_SEEK) {
+ sf_count_t c =
+ time_to_frame(decoder_seek_where(decoder),
+ &audio_format);
+ c = sf_seek(sf, c, SEEK_SET);
+ if (c < 0)
+ decoder_seek_error(decoder);
+ else
+ decoder_command_finished(decoder);
+ cmd = DECODE_COMMAND_NONE;
+ }
+ } while (cmd == DECODE_COMMAND_NONE);
+
+ sf_close(sf);
+}
+
+static bool
+sndfile_scan_file(const char *path_fs,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ SNDFILE *sf;
+ SF_INFO info;
+ const char *p;
+
+ info.format = 0;
+
+ sf = sf_open(path_fs, SFM_READ, &info);
+ if (sf == nullptr)
+ return false;
+
+ if (!audio_valid_sample_rate(info.samplerate)) {
+ sf_close(sf);
+ g_warning("Invalid sample rate in %s\n", path_fs);
+ return false;
+ }
+
+ tag_handler_invoke_duration(handler, handler_ctx,
+ info.frames / info.samplerate);
+
+ p = sf_get_string(sf, SF_STR_TITLE);
+ if (p != nullptr)
+ tag_handler_invoke_tag(handler, handler_ctx,
+ TAG_TITLE, p);
+
+ p = sf_get_string(sf, SF_STR_ARTIST);
+ if (p != nullptr)
+ tag_handler_invoke_tag(handler, handler_ctx,
+ TAG_ARTIST, p);
+
+ p = sf_get_string(sf, SF_STR_DATE);
+ if (p != nullptr)
+ tag_handler_invoke_tag(handler, handler_ctx,
+ TAG_DATE, p);
+
+ sf_close(sf);
+
+ return true;
+}
+
+static const char *const sndfile_suffixes[] = {
+ "wav", "aiff", "aif", /* Microsoft / SGI / Apple */
+ "au", "snd", /* Sun / DEC / NeXT */
+ "paf", /* Paris Audio File */
+ "iff", "svx", /* Commodore Amiga IFF / SVX */
+ "sf", /* IRCAM */
+ "voc", /* Creative */
+ "w64", /* Soundforge */
+ "pvf", /* Portable Voice Format */
+ "xi", /* Fasttracker */
+ "htk", /* HMM Tool Kit */
+ "caf", /* Apple */
+ "sd2", /* Sound Designer II */
+
+ /* libsndfile also supports FLAC and Ogg Vorbis, but only by
+ linking with libFLAC and libvorbis - we can do better, we
+ have native plugins for these libraries */
+
+ nullptr
+};
+
+static const char *const sndfile_mime_types[] = {
+ "audio/x-wav",
+ "audio/x-aiff",
+
+ /* what are the MIME types of the other supported formats? */
+
+ nullptr
+};
+
+const struct decoder_plugin sndfile_decoder_plugin = {
+ "sndfile",
+ nullptr,
+ nullptr,
+ sndfile_stream_decode,
+ nullptr,
+ sndfile_scan_file,
+ nullptr,
+ nullptr,
+ sndfile_suffixes,
+ sndfile_mime_types,
+};
diff --git a/src/decoder/SndfileDecoderPlugin.hxx b/src/decoder/SndfileDecoderPlugin.hxx
new file mode 100644
index 000000000..ba60fafd0
--- /dev/null
+++ b/src/decoder/SndfileDecoderPlugin.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_DECODER_SNDFILE_HXX
+#define MPD_DECODER_SNDFILE_HXX
+
+extern const struct decoder_plugin sndfile_decoder_plugin;
+
+#endif
diff --git a/src/decoder/VorbisComments.cxx b/src/decoder/VorbisComments.cxx
new file mode 100644
index 000000000..88a8dc772
--- /dev/null
+++ b/src/decoder/VorbisComments.cxx
@@ -0,0 +1,150 @@
+/*
+ * 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 "VorbisComments.hxx"
+#include "XiphTags.hxx"
+#include "Tag.hxx"
+#include "TagTable.hxx"
+#include "TagHandler.hxx"
+#include "replay_gain_info.h"
+
+#include <glib.h>
+#include <assert.h>
+#include <stddef.h>
+#include <string.h>
+#include <stdlib.h>
+
+static const char *
+vorbis_comment_value(const char *comment, const char *needle)
+{
+ size_t len = strlen(needle);
+
+ if (g_ascii_strncasecmp(comment, needle, len) == 0 &&
+ comment[len] == '=')
+ return comment + len + 1;
+
+ return NULL;
+}
+
+bool
+vorbis_comments_to_replay_gain(struct replay_gain_info *rgi, char **comments)
+{
+ const char *temp;
+ bool found = false;
+
+ replay_gain_info_init(rgi);
+
+ while (*comments) {
+ if ((temp =
+ vorbis_comment_value(*comments, "replaygain_track_gain"))) {
+ rgi->tuples[REPLAY_GAIN_TRACK].gain = atof(temp);
+ found = true;
+ } else if ((temp = vorbis_comment_value(*comments,
+ "replaygain_album_gain"))) {
+ rgi->tuples[REPLAY_GAIN_ALBUM].gain = atof(temp);
+ found = true;
+ } else if ((temp = vorbis_comment_value(*comments,
+ "replaygain_track_peak"))) {
+ rgi->tuples[REPLAY_GAIN_TRACK].peak = atof(temp);
+ found = true;
+ } else if ((temp = vorbis_comment_value(*comments,
+ "replaygain_album_peak"))) {
+ rgi->tuples[REPLAY_GAIN_ALBUM].peak = atof(temp);
+ found = true;
+ }
+
+ comments++;
+ }
+
+ return found;
+}
+
+/**
+ * Check if the comment's name equals the passed name, and if so, copy
+ * the comment value into the tag.
+ */
+static bool
+vorbis_copy_comment(const char *comment,
+ const char *name, enum tag_type tag_type,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ const char *value;
+
+ value = vorbis_comment_value(comment, name);
+ if (value != NULL) {
+ tag_handler_invoke_tag(handler, handler_ctx, tag_type, value);
+ return true;
+ }
+
+ return false;
+}
+
+static void
+vorbis_scan_comment(const char *comment,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ if (handler->pair != NULL) {
+ char *name = g_strdup((const char*)comment);
+ char *value = strchr(name, '=');
+
+ if (value != NULL && value > name) {
+ *value++ = 0;
+ tag_handler_invoke_pair(handler, handler_ctx,
+ name, value);
+ }
+
+ g_free(name);
+ }
+
+ for (const struct tag_table *i = xiph_tags; i->name != NULL; ++i)
+ if (vorbis_copy_comment(comment, i->name, i->type,
+ handler, handler_ctx))
+ return;
+
+ for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
+ if (vorbis_copy_comment(comment,
+ tag_item_names[i], tag_type(i),
+ handler, handler_ctx))
+ return;
+}
+
+void
+vorbis_comments_scan(char **comments,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ while (*comments)
+ vorbis_scan_comment(*comments++,
+ handler, handler_ctx);
+
+}
+
+Tag *
+vorbis_comments_to_tag(char **comments)
+{
+ Tag *tag = new Tag();
+ vorbis_comments_scan(comments, &add_tag_handler, tag);
+
+ if (tag->IsEmpty()) {
+ delete tag;
+ tag = NULL;
+ }
+
+ return tag;
+}
diff --git a/src/decoder/VorbisComments.hxx b/src/decoder/VorbisComments.hxx
new file mode 100644
index 000000000..7a8374785
--- /dev/null
+++ b/src/decoder/VorbisComments.hxx
@@ -0,0 +1,39 @@
+/*
+ * 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_VORBIS_COMMENTS_HXX
+#define MPD_VORBIS_COMMENTS_HXX
+
+#include "check.h"
+
+struct replay_gain_info;
+struct tag_handler;
+struct Tag;
+
+bool
+vorbis_comments_to_replay_gain(struct replay_gain_info *rgi, char **comments);
+
+void
+vorbis_comments_scan(char **comments,
+ const struct tag_handler *handler, void *handler_ctx);
+
+Tag *
+vorbis_comments_to_tag(char **comments);
+
+#endif
diff --git a/src/decoder/VorbisDecoderPlugin.cxx b/src/decoder/VorbisDecoderPlugin.cxx
new file mode 100644
index 000000000..f51480d71
--- /dev/null
+++ b/src/decoder/VorbisDecoderPlugin.cxx
@@ -0,0 +1,357 @@
+/*
+ * 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.
+ */
+
+#include "config.h"
+#include "VorbisDecoderPlugin.h"
+#include "VorbisComments.hxx"
+#include "DecoderAPI.hxx"
+#include "InputStream.hxx"
+#include "OggCodec.hxx"
+#include "util/UriUtil.hxx"
+#include "CheckAudioFormat.hxx"
+#include "TagHandler.hxx"
+
+#ifndef HAVE_TREMOR
+#define OV_EXCLUDE_STATIC_CALLBACKS
+#include <vorbis/vorbisfile.h>
+#else
+#include <tremor/ivorbisfile.h>
+/* Macros to make Tremor's API look like libogg. Tremor always
+ returns host-byte-order 16-bit signed data, and uses integer
+ milliseconds where libogg uses double seconds.
+*/
+#define ov_read(VF, BUFFER, LENGTH, BIGENDIANP, WORD, SGNED, BITSTREAM) \
+ ov_read(VF, BUFFER, LENGTH, BITSTREAM)
+#define ov_time_total(VF, I) ((double)ov_time_total(VF, I)/1000)
+#define ov_time_tell(VF) ((double)ov_time_tell(VF)/1000)
+#define ov_time_seek_page(VF, S) (ov_time_seek_page(VF, (S)*1000))
+#endif /* HAVE_TREMOR */
+
+#include <glib.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <unistd.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "vorbis"
+
+#if G_BYTE_ORDER == G_BIG_ENDIAN
+#define VORBIS_BIG_ENDIAN true
+#else
+#define VORBIS_BIG_ENDIAN false
+#endif
+
+struct vorbis_input_stream {
+ struct decoder *decoder;
+
+ struct input_stream *input_stream;
+ bool seekable;
+};
+
+static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *data)
+{
+ struct vorbis_input_stream *vis = (struct vorbis_input_stream *)data;
+ size_t ret = decoder_read(vis->decoder, vis->input_stream,
+ ptr, size * nmemb);
+
+ errno = 0;
+
+ return ret / size;
+}
+
+static int ogg_seek_cb(void *data, ogg_int64_t offset, int whence)
+{
+ struct vorbis_input_stream *vis = (struct vorbis_input_stream *)data;
+
+ return vis->seekable &&
+ (!vis->decoder || decoder_get_command(vis->decoder) != DECODE_COMMAND_STOP) &&
+ input_stream_lock_seek(vis->input_stream, offset, whence, NULL)
+ ? 0 : -1;
+}
+
+/* TODO: check Ogg libraries API and see if we can just not have this func */
+static int ogg_close_cb(G_GNUC_UNUSED void *data)
+{
+ return 0;
+}
+
+static long ogg_tell_cb(void *data)
+{
+ struct vorbis_input_stream *vis = (struct vorbis_input_stream *)data;
+
+ return (long)vis->input_stream->offset;
+}
+
+static const ov_callbacks vorbis_is_callbacks = {
+ ogg_read_cb,
+ ogg_seek_cb,
+ ogg_close_cb,
+ ogg_tell_cb,
+};
+
+static const char *
+vorbis_strerror(int code)
+{
+ switch (code) {
+ case OV_EREAD:
+ return "read error";
+
+ case OV_ENOTVORBIS:
+ return "not vorbis stream";
+
+ case OV_EVERSION:
+ return "vorbis version mismatch";
+
+ case OV_EBADHEADER:
+ return "invalid vorbis header";
+
+ case OV_EFAULT:
+ return "internal logic error";
+
+ default:
+ return "unknown error";
+ }
+}
+
+static bool
+vorbis_is_open(struct vorbis_input_stream *vis, OggVorbis_File *vf,
+ struct decoder *decoder, struct input_stream *input_stream)
+{
+ vis->decoder = decoder;
+ vis->input_stream = input_stream;
+ vis->seekable = input_stream_cheap_seeking(input_stream);
+
+ int ret = ov_open_callbacks(vis, vf, NULL, 0, vorbis_is_callbacks);
+ if (ret < 0) {
+ if (decoder == NULL ||
+ decoder_get_command(decoder) == DECODE_COMMAND_NONE)
+ g_warning("Failed to open Ogg Vorbis stream: %s",
+ vorbis_strerror(ret));
+ return false;
+ }
+
+ return true;
+}
+
+static void
+vorbis_send_comments(struct decoder *decoder, struct input_stream *is,
+ char **comments)
+{
+ Tag *tag = vorbis_comments_to_tag(comments);
+ if (!tag)
+ return;
+
+ decoder_tag(decoder, is, std::move(*tag));
+ delete tag;
+}
+
+#ifndef HAVE_TREMOR
+static void
+vorbis_interleave(float *dest, const float *const*src,
+ unsigned nframes, unsigned channels)
+{
+ for (const float *const*src_end = src + channels;
+ src != src_end; ++src, ++dest) {
+ float *d = dest;
+ for (const float *s = *src, *s_end = s + nframes;
+ s != s_end; ++s, d += channels)
+ *d = *s;
+ }
+}
+#endif
+
+/* public */
+static void
+vorbis_stream_decode(struct decoder *decoder,
+ struct input_stream *input_stream)
+{
+ GError *error = NULL;
+
+ if (ogg_codec_detect(decoder, input_stream) != OGG_CODEC_VORBIS)
+ return;
+
+ /* rewind the stream, because ogg_codec_detect() has
+ moved it */
+ input_stream_lock_seek(input_stream, 0, SEEK_SET, NULL);
+
+ struct vorbis_input_stream vis;
+ OggVorbis_File vf;
+ if (!vorbis_is_open(&vis, &vf, decoder, input_stream))
+ return;
+
+ const vorbis_info *vi = ov_info(&vf, -1);
+ if (vi == NULL) {
+ g_warning("ov_info() has failed");
+ return;
+ }
+
+ AudioFormat audio_format;
+ if (!audio_format_init_checked(audio_format, vi->rate,
+#ifdef HAVE_TREMOR
+ SampleFormat::S16,
+#else
+ SampleFormat::FLOAT,
+#endif
+ vi->channels, &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return;
+ }
+
+ float total_time = ov_time_total(&vf, -1);
+ if (total_time < 0)
+ total_time = 0;
+
+ decoder_initialized(decoder, audio_format, vis.seekable, total_time);
+
+ enum decoder_command cmd = decoder_get_command(decoder);
+
+#ifdef HAVE_TREMOR
+ char buffer[4096];
+#else
+ float buffer[2048];
+ const int frames_per_buffer =
+ G_N_ELEMENTS(buffer) / audio_format.channels;
+ const unsigned frame_size = sizeof(buffer[0]) * audio_format.channels;
+#endif
+
+ int prev_section = -1;
+ unsigned kbit_rate = 0;
+
+ do {
+ if (cmd == DECODE_COMMAND_SEEK) {
+ double seek_where = decoder_seek_where(decoder);
+ if (0 == ov_time_seek_page(&vf, seek_where)) {
+ decoder_command_finished(decoder);
+ } else
+ decoder_seek_error(decoder);
+ }
+
+ int current_section;
+
+#ifdef HAVE_TREMOR
+ long nbytes = ov_read(&vf, buffer, sizeof(buffer),
+ VORBIS_BIG_ENDIAN, 2, 1,
+ &current_section);
+#else
+ float **per_channel;
+ long nframes = ov_read_float(&vf, &per_channel,
+ frames_per_buffer,
+ &current_section);
+ long nbytes = nframes;
+ if (nframes > 0) {
+ vorbis_interleave(buffer,
+ (const float*const*)per_channel,
+ nframes, audio_format.channels);
+ nbytes *= frame_size;
+ }
+#endif
+
+ if (nbytes == OV_HOLE) /* bad packet */
+ nbytes = 0;
+ else if (nbytes <= 0)
+ /* break on EOF or other error */
+ break;
+
+ if (current_section != prev_section) {
+ vi = ov_info(&vf, -1);
+ if (vi == NULL) {
+ g_warning("ov_info() has failed");
+ break;
+ }
+
+ if (vi->rate != (long)audio_format.sample_rate ||
+ vi->channels != (int)audio_format.channels) {
+ /* we don't support audio format
+ change yet */
+ g_warning("audio format change, stopping here");
+ break;
+ }
+
+ char **comments = ov_comment(&vf, -1)->user_comments;
+ vorbis_send_comments(decoder, input_stream, comments);
+
+ struct replay_gain_info rgi;
+ if (vorbis_comments_to_replay_gain(&rgi, comments))
+ decoder_replay_gain(decoder, &rgi);
+
+ prev_section = current_section;
+ }
+
+ long test = ov_bitrate_instant(&vf);
+ if (test > 0)
+ kbit_rate = test / 1000;
+
+ cmd = decoder_data(decoder, input_stream,
+ buffer, nbytes,
+ kbit_rate);
+ } while (cmd != DECODE_COMMAND_STOP);
+
+ ov_clear(&vf);
+}
+
+static bool
+vorbis_scan_stream(struct input_stream *is,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ struct vorbis_input_stream vis;
+ OggVorbis_File vf;
+
+ if (!vorbis_is_open(&vis, &vf, NULL, is))
+ return false;
+
+ tag_handler_invoke_duration(handler, handler_ctx,
+ (int)(ov_time_total(&vf, -1) + 0.5));
+
+ vorbis_comments_scan(ov_comment(&vf, -1)->user_comments,
+ handler, handler_ctx);
+
+ ov_clear(&vf);
+ return true;
+}
+
+static const char *const vorbis_suffixes[] = {
+ "ogg", "oga", NULL
+};
+
+static const char *const vorbis_mime_types[] = {
+ "application/ogg",
+ "application/x-ogg",
+ "audio/ogg",
+ "audio/vorbis",
+ "audio/vorbis+ogg",
+ "audio/x-ogg",
+ "audio/x-vorbis",
+ "audio/x-vorbis+ogg",
+ NULL
+};
+
+const struct decoder_plugin vorbis_decoder_plugin = {
+ "vorbis",
+ nullptr,
+ nullptr,
+ vorbis_stream_decode,
+ nullptr,
+ nullptr,
+ vorbis_scan_stream,
+ nullptr,
+ vorbis_suffixes,
+ vorbis_mime_types
+};
diff --git a/src/decoder/VorbisDecoderPlugin.h b/src/decoder/VorbisDecoderPlugin.h
new file mode 100644
index 000000000..618c9ffde
--- /dev/null
+++ b/src/decoder/VorbisDecoderPlugin.h
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+#ifndef MPD_DECODER_VORBIS_H
+#define MPD_DECODER_VORBIS_H
+
+extern const struct decoder_plugin vorbis_decoder_plugin;
+
+#endif
diff --git a/src/decoder/WavpackDecoderPlugin.cxx b/src/decoder/WavpackDecoderPlugin.cxx
new file mode 100644
index 000000000..aa62a0f67
--- /dev/null
+++ b/src/decoder/WavpackDecoderPlugin.cxx
@@ -0,0 +1,599 @@
+/*
+ * 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 "WavpackDecoderPlugin.hxx"
+#include "DecoderAPI.hxx"
+#include "InputStream.hxx"
+#include "CheckAudioFormat.hxx"
+#include "TagHandler.hxx"
+#include "ApeTag.hxx"
+
+#include <wavpack/wavpack.h>
+#include <glib.h>
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "wavpack"
+
+#define ERRORLEN 80
+
+/** A pointer type for format converter function. */
+typedef void (*format_samples_t)(
+ int bytes_per_sample,
+ void *buffer, uint32_t count
+);
+
+/*
+ * This function has been borrowed from the tiny player found on
+ * wavpack.com. Modifications were required because mpd only handles
+ * max 24-bit samples.
+ */
+static void
+format_samples_int(int bytes_per_sample, void *buffer, uint32_t count)
+{
+ int32_t *src = (int32_t *)buffer;
+
+ switch (bytes_per_sample) {
+ case 1: {
+ int8_t *dst = (int8_t *)buffer;
+ /*
+ * The asserts like the following one are because we do the
+ * formatting of samples within a single buffer. The size
+ * of the output samples never can be greater than the size
+ * of the input ones. Otherwise we would have an overflow.
+ */
+ static_assert(sizeof(*dst) <= sizeof(*src), "Wrong size");
+
+ /* pass through and align 8-bit samples */
+ while (count--) {
+ *dst++ = *src++;
+ }
+ break;
+ }
+ case 2: {
+ uint16_t *dst = (uint16_t *)buffer;
+ static_assert(sizeof(*dst) <= sizeof(*src), "Wrong size");
+
+ /* pass through and align 16-bit samples */
+ while (count--) {
+ *dst++ = *src++;
+ }
+ break;
+ }
+
+ case 3:
+ case 4:
+ /* do nothing */
+ break;
+ }
+}
+
+/*
+ * This function converts floating point sample data to 24-bit integer.
+ */
+static void
+format_samples_float(G_GNUC_UNUSED int bytes_per_sample, void *buffer,
+ uint32_t count)
+{
+ float *p = (float *)buffer;
+
+ while (count--) {
+ *p /= (1 << 23);
+ ++p;
+ }
+}
+
+/**
+ * Choose a MPD sample format from libwavpacks' number of bits.
+ */
+static SampleFormat
+wavpack_bits_to_sample_format(bool is_float, int bytes_per_sample)
+{
+ if (is_float)
+ return SampleFormat::FLOAT;
+
+ switch (bytes_per_sample) {
+ case 1:
+ return SampleFormat::S8;
+
+ case 2:
+ return SampleFormat::S16;
+
+ case 3:
+ return SampleFormat::S24_P32;
+
+ case 4:
+ return SampleFormat::S32;
+
+ default:
+ return SampleFormat::UNDEFINED;
+ }
+}
+
+/*
+ * This does the main decoding thing.
+ * Requires an already opened WavpackContext.
+ */
+static void
+wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek)
+{
+ GError *error = NULL;
+ bool is_float;
+ SampleFormat sample_format;
+ AudioFormat audio_format;
+ format_samples_t format_samples;
+ float total_time;
+ int bytes_per_sample, output_sample_size;
+
+ is_float = (WavpackGetMode(wpc) & MODE_FLOAT) != 0;
+ sample_format =
+ wavpack_bits_to_sample_format(is_float,
+ WavpackGetBytesPerSample(wpc));
+
+ if (!audio_format_init_checked(audio_format,
+ WavpackGetSampleRate(wpc),
+ sample_format,
+ WavpackGetNumChannels(wpc), &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return;
+ }
+
+ if (is_float) {
+ format_samples = format_samples_float;
+ } else {
+ format_samples = format_samples_int;
+ }
+
+ total_time = WavpackGetNumSamples(wpc);
+ total_time /= audio_format.sample_rate;
+ bytes_per_sample = WavpackGetBytesPerSample(wpc);
+ output_sample_size = audio_format.GetFrameSize();
+
+ /* wavpack gives us all kind of samples in a 32-bit space */
+ int32_t chunk[1024];
+ const uint32_t samples_requested = G_N_ELEMENTS(chunk) /
+ audio_format.channels;
+
+ decoder_initialized(decoder, audio_format, can_seek, total_time);
+
+ enum decoder_command cmd = decoder_get_command(decoder);
+ while (cmd != DECODE_COMMAND_STOP) {
+ if (cmd == DECODE_COMMAND_SEEK) {
+ if (can_seek) {
+ unsigned where = decoder_seek_where(decoder) *
+ audio_format.sample_rate;
+
+ if (WavpackSeekSample(wpc, where)) {
+ decoder_command_finished(decoder);
+ } else {
+ decoder_seek_error(decoder);
+ }
+ } else {
+ decoder_seek_error(decoder);
+ }
+ }
+
+ uint32_t samples_got = WavpackUnpackSamples(wpc, chunk,
+ samples_requested);
+ if (samples_got == 0)
+ break;
+
+ int bitrate = (int)(WavpackGetInstantBitrate(wpc) / 1000 +
+ 0.5);
+ format_samples(bytes_per_sample, chunk,
+ samples_got * audio_format.channels);
+
+ cmd = decoder_data(decoder, NULL, chunk,
+ samples_got * output_sample_size,
+ bitrate);
+ }
+}
+
+/**
+ * Locate and parse a floating point tag. Returns true if it was
+ * found.
+ */
+static bool
+wavpack_tag_float(WavpackContext *wpc, const char *key, float *value_r)
+{
+ char buffer[64];
+ int ret;
+
+ ret = WavpackGetTagItem(wpc, key, buffer, sizeof(buffer));
+ if (ret <= 0)
+ return false;
+
+ *value_r = atof(buffer);
+ return true;
+}
+
+static bool
+wavpack_replaygain(struct replay_gain_info *replay_gain_info,
+ WavpackContext *wpc)
+{
+ bool found = false;
+
+ replay_gain_info_init(replay_gain_info);
+
+ found |= wavpack_tag_float(
+ wpc, "replaygain_track_gain",
+ &replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain
+ );
+ found |= wavpack_tag_float(
+ wpc, "replaygain_track_peak",
+ &replay_gain_info->tuples[REPLAY_GAIN_TRACK].peak
+ );
+ found |= wavpack_tag_float(
+ wpc, "replaygain_album_gain",
+ &replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain
+ );
+ found |= wavpack_tag_float(
+ wpc, "replaygain_album_peak",
+ &replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak
+ );
+
+ return found;
+}
+
+static void
+wavpack_scan_tag_item(WavpackContext *wpc, const char *name,
+ enum tag_type type,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ char buffer[1024];
+ int len = WavpackGetTagItem(wpc, name, buffer, sizeof(buffer));
+ if (len <= 0 || (unsigned)len >= sizeof(buffer))
+ return;
+
+ tag_handler_invoke_tag(handler, handler_ctx, type, buffer);
+
+}
+
+static void
+wavpack_scan_pair(WavpackContext *wpc, const char *name,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ char buffer[8192];
+ int len = WavpackGetTagItem(wpc, name, buffer, sizeof(buffer));
+ if (len <= 0 || (unsigned)len >= sizeof(buffer))
+ return;
+
+ tag_handler_invoke_pair(handler, handler_ctx, name, buffer);
+}
+
+/*
+ * Reads metainfo from the specified file.
+ */
+static bool
+wavpack_scan_file(const char *fname,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ WavpackContext *wpc;
+ char error[ERRORLEN];
+
+ wpc = WavpackOpenFileInput(fname, error, OPEN_TAGS, 0);
+ if (wpc == NULL) {
+ g_warning(
+ "failed to open WavPack file \"%s\": %s\n",
+ fname, error
+ );
+ return false;
+ }
+
+ tag_handler_invoke_duration(handler, handler_ctx,
+ WavpackGetNumSamples(wpc) /
+ WavpackGetSampleRate(wpc));
+
+ /* the WavPack format implies APEv2 tags, which means we can
+ reuse the mapping from tag_ape.c */
+
+ for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) {
+ const char *name = tag_item_names[i];
+ if (name != NULL)
+ wavpack_scan_tag_item(wpc, name, (enum tag_type)i,
+ handler, handler_ctx);
+ }
+
+ for (const struct tag_table *i = ape_tags; i->name != NULL; ++i)
+ wavpack_scan_tag_item(wpc, i->name, i->type,
+ handler, handler_ctx);
+
+ if (handler->pair != NULL) {
+ char name[64];
+
+ for (int i = 0, n = WavpackGetNumTagItems(wpc);
+ i < n; ++i) {
+ int len = WavpackGetTagItemIndexed(wpc, i, name,
+ sizeof(name));
+ if (len <= 0 || (unsigned)len >= sizeof(name))
+ continue;
+
+ wavpack_scan_pair(wpc, name, handler, handler_ctx);
+ }
+ }
+
+ WavpackCloseFile(wpc);
+
+ return true;
+}
+
+/*
+ * mpd input_stream <=> WavpackStreamReader wrapper callbacks
+ */
+
+/* This struct is needed for per-stream last_byte storage. */
+struct wavpack_input {
+ struct decoder *decoder;
+ struct input_stream *is;
+ /* Needed for push_back_byte() */
+ int last_byte;
+};
+
+/**
+ * Little wrapper for struct wavpack_input to cast from void *.
+ */
+static struct wavpack_input *
+wpin(void *id)
+{
+ assert(id);
+ return (struct wavpack_input *)id;
+}
+
+static int32_t
+wavpack_input_read_bytes(void *id, void *data, int32_t bcount)
+{
+ uint8_t *buf = (uint8_t *)data;
+ int32_t i = 0;
+
+ if (wpin(id)->last_byte != EOF) {
+ *buf++ = wpin(id)->last_byte;
+ wpin(id)->last_byte = EOF;
+ --bcount;
+ ++i;
+ }
+
+ /* wavpack fails if we return a partial read, so we just wait
+ until the buffer is full */
+ while (bcount > 0) {
+ size_t nbytes = decoder_read(
+ wpin(id)->decoder, wpin(id)->is, buf, bcount
+ );
+ if (nbytes == 0) {
+ /* EOF, error or a decoder command */
+ break;
+ }
+
+ i += nbytes;
+ bcount -= nbytes;
+ buf += nbytes;
+ }
+
+ return i;
+}
+
+static uint32_t
+wavpack_input_get_pos(void *id)
+{
+ return wpin(id)->is->offset;
+}
+
+static int
+wavpack_input_set_pos_abs(void *id, uint32_t pos)
+{
+ return input_stream_lock_seek(wpin(id)->is, pos, SEEK_SET, NULL)
+ ? 0 : -1;
+}
+
+static int
+wavpack_input_set_pos_rel(void *id, int32_t delta, int mode)
+{
+ return input_stream_lock_seek(wpin(id)->is, delta, mode, NULL)
+ ? 0 : -1;
+}
+
+static int
+wavpack_input_push_back_byte(void *id, int c)
+{
+ if (wpin(id)->last_byte == EOF) {
+ wpin(id)->last_byte = c;
+ return c;
+ } else {
+ return EOF;
+ }
+}
+
+static uint32_t
+wavpack_input_get_length(void *id)
+{
+ if (wpin(id)->is->size < 0)
+ return 0;
+
+ return wpin(id)->is->size;
+}
+
+static int
+wavpack_input_can_seek(void *id)
+{
+ return wpin(id)->is->seekable;
+}
+
+static WavpackStreamReader mpd_is_reader = {
+ wavpack_input_read_bytes,
+ wavpack_input_get_pos,
+ wavpack_input_set_pos_abs,
+ wavpack_input_set_pos_rel,
+ wavpack_input_push_back_byte,
+ wavpack_input_get_length,
+ wavpack_input_can_seek,
+ nullptr /* no need to write edited tags */
+};
+
+static void
+wavpack_input_init(struct wavpack_input *isp, struct decoder *decoder,
+ struct input_stream *is)
+{
+ isp->decoder = decoder;
+ isp->is = is;
+ isp->last_byte = EOF;
+}
+
+static struct input_stream *
+wavpack_open_wvc(struct decoder *decoder, const char *uri,
+ Mutex &mutex, Cond &cond,
+ struct wavpack_input *wpi)
+{
+ struct input_stream *is_wvc;
+ char *wvc_url = NULL;
+ char first_byte;
+ size_t nbytes;
+
+ /*
+ * As we use dc->utf8url, this function will be bad for
+ * single files. utf8url is not absolute file path :/
+ */
+ if (uri == NULL)
+ return nullptr;
+
+ wvc_url = g_strconcat(uri, "c", NULL);
+ is_wvc = input_stream_open(wvc_url, mutex, cond, NULL);
+ g_free(wvc_url);
+
+ if (is_wvc == NULL)
+ return NULL;
+
+ /*
+ * And we try to buffer in order to get know
+ * about a possible 404 error.
+ */
+ nbytes = decoder_read(
+ decoder, is_wvc, &first_byte, sizeof(first_byte)
+ );
+ if (nbytes == 0) {
+ input_stream_close(is_wvc);
+ return NULL;
+ }
+
+ /* push it back */
+ wavpack_input_init(wpi, decoder, is_wvc);
+ wpi->last_byte = first_byte;
+ return is_wvc;
+}
+
+/*
+ * Decodes a stream.
+ */
+static void
+wavpack_streamdecode(struct decoder * decoder, struct input_stream *is)
+{
+ char error[ERRORLEN];
+ WavpackContext *wpc;
+ struct input_stream *is_wvc;
+ int open_flags = OPEN_NORMALIZE;
+ struct wavpack_input isp, isp_wvc;
+ bool can_seek = is->seekable;
+
+ is_wvc = wavpack_open_wvc(decoder, is->uri.c_str(),
+ is->mutex, is->cond,
+ &isp_wvc);
+ if (is_wvc != NULL) {
+ open_flags |= OPEN_WVC;
+ can_seek &= is_wvc->seekable;
+ }
+
+ if (!can_seek) {
+ open_flags |= OPEN_STREAMING;
+ }
+
+ wavpack_input_init(&isp, decoder, is);
+ wpc = WavpackOpenFileInputEx(
+ &mpd_is_reader, &isp,
+ open_flags & OPEN_WVC ? &isp_wvc : NULL,
+ error, open_flags, 23
+ );
+
+ if (wpc == NULL) {
+ g_warning("failed to open WavPack stream: %s\n", error);
+ return;
+ }
+
+ wavpack_decode(decoder, wpc, can_seek);
+
+ WavpackCloseFile(wpc);
+ if (open_flags & OPEN_WVC) {
+ input_stream_close(is_wvc);
+ }
+}
+
+/*
+ * Decodes a file.
+ */
+static void
+wavpack_filedecode(struct decoder *decoder, const char *fname)
+{
+ char error[ERRORLEN];
+ WavpackContext *wpc;
+
+ wpc = WavpackOpenFileInput(
+ fname, error,
+ OPEN_TAGS | OPEN_WVC | OPEN_NORMALIZE, 23
+ );
+ if (wpc == NULL) {
+ g_warning(
+ "failed to open WavPack file \"%s\": %s\n",
+ fname, error
+ );
+ return;
+ }
+
+ struct replay_gain_info replay_gain_info;
+ if (wavpack_replaygain(&replay_gain_info, wpc))
+ decoder_replay_gain(decoder, &replay_gain_info);
+
+ wavpack_decode(decoder, wpc, true);
+
+ WavpackCloseFile(wpc);
+}
+
+static char const *const wavpack_suffixes[] = {
+ "wv",
+ NULL
+};
+
+static char const *const wavpack_mime_types[] = {
+ "audio/x-wavpack",
+ NULL
+};
+
+const struct decoder_plugin wavpack_decoder_plugin = {
+ "wavpack",
+ nullptr,
+ nullptr,
+ wavpack_streamdecode,
+ wavpack_filedecode,
+ wavpack_scan_file,
+ nullptr,
+ nullptr,
+ wavpack_suffixes,
+ wavpack_mime_types
+};
diff --git a/src/decoder/WavpackDecoderPlugin.hxx b/src/decoder/WavpackDecoderPlugin.hxx
new file mode 100644
index 000000000..9ebe6354f
--- /dev/null
+++ b/src/decoder/WavpackDecoderPlugin.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_DECODER_WAVPACK_HXX
+#define MPD_DECODER_WAVPACK_HXX
+
+extern const struct decoder_plugin wavpack_decoder_plugin;
+
+#endif
diff --git a/src/decoder/WildmidiDecoderPlugin.cxx b/src/decoder/WildmidiDecoderPlugin.cxx
new file mode 100644
index 000000000..1358c20d3
--- /dev/null
+++ b/src/decoder/WildmidiDecoderPlugin.cxx
@@ -0,0 +1,155 @@
+/*
+ * 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 "WildmidiDecoderPlugin.hxx"
+#include "DecoderAPI.hxx"
+#include "TagHandler.hxx"
+#include "glib_compat.h"
+
+#include <glib.h>
+
+extern "C" {
+#include <wildmidi_lib.h>
+}
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "wildmidi"
+
+static constexpr unsigned WILDMIDI_SAMPLE_RATE = 48000;
+
+static bool
+wildmidi_init(const config_param &param)
+{
+ const char *config_file;
+ int ret;
+
+ config_file = param.GetBlockValue("config_file",
+ "/etc/timidity/timidity.cfg");
+ if (!g_file_test(config_file, G_FILE_TEST_IS_REGULAR)) {
+ g_debug("configuration file does not exist: %s", config_file);
+ return false;
+ }
+
+ ret = WildMidi_Init(config_file, WILDMIDI_SAMPLE_RATE, 0);
+ return ret == 0;
+}
+
+static void
+wildmidi_finish(void)
+{
+ WildMidi_Shutdown();
+}
+
+static void
+wildmidi_file_decode(struct decoder *decoder, const char *path_fs)
+{
+ static constexpr AudioFormat audio_format = {
+ WILDMIDI_SAMPLE_RATE,
+ SampleFormat::S16,
+ 2,
+ };
+ midi *wm;
+ const struct _WM_Info *info;
+ enum decoder_command cmd;
+
+ wm = WildMidi_Open(path_fs);
+ if (wm == nullptr)
+ return;
+
+ info = WildMidi_GetInfo(wm);
+ if (info == nullptr) {
+ WildMidi_Close(wm);
+ return;
+ }
+
+ decoder_initialized(decoder, audio_format, true,
+ info->approx_total_samples / WILDMIDI_SAMPLE_RATE);
+
+ do {
+ char buffer[4096];
+ int len;
+
+ info = WildMidi_GetInfo(wm);
+ if (info == nullptr)
+ break;
+
+ len = WildMidi_GetOutput(wm, buffer, sizeof(buffer));
+ if (len <= 0)
+ break;
+
+ cmd = decoder_data(decoder, nullptr, buffer, len, 0);
+
+ if (cmd == DECODE_COMMAND_SEEK) {
+ unsigned long seek_where = WILDMIDI_SAMPLE_RATE *
+ decoder_seek_where(decoder);
+
+#ifdef HAVE_WILDMIDI_SAMPLED_SEEK
+ WildMidi_SampledSeek(wm, &seek_where);
+#else
+ WildMidi_FastSeek(wm, &seek_where);
+#endif
+ decoder_command_finished(decoder);
+ cmd = DECODE_COMMAND_NONE;
+ }
+
+ } while (cmd == DECODE_COMMAND_NONE);
+
+ WildMidi_Close(wm);
+}
+
+static bool
+wildmidi_scan_file(const char *path_fs,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ midi *wm = WildMidi_Open(path_fs);
+ if (wm == nullptr)
+ return false;
+
+ const struct _WM_Info *info = WildMidi_GetInfo(wm);
+ if (info == nullptr) {
+ WildMidi_Close(wm);
+ return false;
+ }
+
+ int duration = info->approx_total_samples / WILDMIDI_SAMPLE_RATE;
+ tag_handler_invoke_duration(handler, handler_ctx, duration);
+
+ WildMidi_Close(wm);
+
+ return true;
+}
+
+static const char *const wildmidi_suffixes[] = {
+ "mid",
+ nullptr
+};
+
+const struct decoder_plugin wildmidi_decoder_plugin = {
+ "wildmidi",
+ wildmidi_init,
+ wildmidi_finish,
+ nullptr,
+ wildmidi_file_decode,
+ wildmidi_scan_file,
+ nullptr,
+ nullptr,
+ wildmidi_suffixes,
+ nullptr,
+};
diff --git a/src/decoder/WildmidiDecoderPlugin.hxx b/src/decoder/WildmidiDecoderPlugin.hxx
new file mode 100644
index 000000000..956b72299
--- /dev/null
+++ b/src/decoder/WildmidiDecoderPlugin.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_DECODER_WILDMIDI_HXX
+#define MPD_DECODER_WILDMIDI_HXX
+
+extern const struct decoder_plugin wildmidi_decoder_plugin;
+
+#endif
diff --git a/src/decoder/XiphTags.cxx b/src/decoder/XiphTags.cxx
new file mode 100644
index 000000000..b2aa6e82d
--- /dev/null
+++ b/src/decoder/XiphTags.cxx
@@ -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.
+ */
+
+#include "config.h"
+#include "XiphTags.hxx"
+
+const struct tag_table xiph_tags[] = {
+ { "tracknumber", TAG_TRACK },
+ { "discnumber", TAG_DISC },
+ { "album artist", TAG_ALBUM_ARTIST },
+ { NULL, TAG_NUM_OF_ITEM_TYPES }
+};
diff --git a/src/decoder/XiphTags.hxx b/src/decoder/XiphTags.hxx
new file mode 100644
index 000000000..43a1d3ec7
--- /dev/null
+++ b/src/decoder/XiphTags.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_XIPH_TAGS_HXX
+#define MPD_XIPH_TAGS_HXX
+
+#include "check.h"
+#include "TagTable.hxx"
+
+extern const struct tag_table xiph_tags[];
+
+#endif
diff --git a/src/decoder/_flac_common.c b/src/decoder/_flac_common.c
deleted file mode 100644
index d7f0c4a8a..000000000
--- a/src/decoder/_flac_common.c
+++ /dev/null
@@ -1,228 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * Common data structures and functions used by FLAC and OggFLAC
- */
-
-#include "config.h"
-#include "_flac_common.h"
-#include "flac_metadata.h"
-#include "flac_pcm.h"
-#include "audio_check.h"
-
-#include <glib.h>
-
-#include <assert.h>
-
-void
-flac_data_init(struct flac_data *data, struct decoder * decoder,
- struct input_stream *input_stream)
-{
- pcm_buffer_init(&data->buffer);
-
- data->unsupported = false;
- data->initialized = false;
- data->total_frames = 0;
- data->first_frame = 0;
- data->next_frame = 0;
-
- data->position = 0;
- data->decoder = decoder;
- data->input_stream = input_stream;
- data->tag = NULL;
-}
-
-void
-flac_data_deinit(struct flac_data *data)
-{
- pcm_buffer_deinit(&data->buffer);
-
- if (data->tag != NULL)
- tag_free(data->tag);
-}
-
-static enum sample_format
-flac_sample_format(unsigned bits_per_sample)
-{
- switch (bits_per_sample) {
- case 8:
- return SAMPLE_FORMAT_S8;
-
- case 16:
- return SAMPLE_FORMAT_S16;
-
- case 24:
- return SAMPLE_FORMAT_S24_P32;
-
- case 32:
- return SAMPLE_FORMAT_S32;
-
- default:
- return SAMPLE_FORMAT_UNDEFINED;
- }
-}
-
-static void
-flac_got_stream_info(struct flac_data *data,
- const FLAC__StreamMetadata_StreamInfo *stream_info)
-{
- if (data->initialized || data->unsupported)
- return;
-
- GError *error = NULL;
- if (!audio_format_init_checked(&data->audio_format,
- stream_info->sample_rate,
- flac_sample_format(stream_info->bits_per_sample),
- stream_info->channels, &error)) {
- g_warning("%s", error->message);
- g_error_free(error);
- data->unsupported = true;
- return;
- }
-
- data->frame_size = audio_format_frame_size(&data->audio_format);
-
- if (data->total_frames == 0)
- data->total_frames = stream_info->total_samples;
-
- data->initialized = true;
-}
-
-void flac_metadata_common_cb(const FLAC__StreamMetadata * block,
- struct flac_data *data)
-{
- if (data->unsupported)
- return;
-
- struct replay_gain_info rgi;
- char *mixramp_start;
- char *mixramp_end;
- float replay_gain_db = 0;
-
- switch (block->type) {
- case FLAC__METADATA_TYPE_STREAMINFO:
- flac_got_stream_info(data, &block->data.stream_info);
- break;
-
- case FLAC__METADATA_TYPE_VORBIS_COMMENT:
- if (flac_parse_replay_gain(&rgi, block))
- replay_gain_db = decoder_replay_gain(data->decoder, &rgi);
-
- if (flac_parse_mixramp(&mixramp_start, &mixramp_end, block))
- decoder_mixramp(data->decoder, replay_gain_db,
- mixramp_start, mixramp_end);
-
- if (data->tag != NULL)
- flac_vorbis_comments_to_tag(data->tag, NULL,
- &block->data.vorbis_comment);
-
- default:
- break;
- }
-}
-
-void flac_error_common_cb(const FLAC__StreamDecoderErrorStatus status,
- struct flac_data *data)
-{
- if (decoder_get_command(data->decoder) == DECODE_COMMAND_STOP)
- return;
-
- g_warning("%s", FLAC__StreamDecoderErrorStatusString[status]);
-}
-
-/**
- * This function attempts to call decoder_initialized() in case there
- * was no STREAMINFO block. This is allowed for nonseekable streams,
- * where the server sends us only a part of the file, without
- * providing the STREAMINFO block from the beginning of the file
- * (e.g. when seeking with SqueezeBox Server).
- */
-static bool
-flac_got_first_frame(struct flac_data *data, const FLAC__FrameHeader *header)
-{
- if (data->unsupported)
- return false;
-
- GError *error = NULL;
- if (!audio_format_init_checked(&data->audio_format,
- header->sample_rate,
- flac_sample_format(header->bits_per_sample),
- header->channels, &error)) {
- g_warning("%s", error->message);
- g_error_free(error);
- data->unsupported = true;
- return false;
- }
-
- data->frame_size = audio_format_frame_size(&data->audio_format);
-
- decoder_initialized(data->decoder, &data->audio_format,
- data->input_stream->seekable,
- (float)data->total_frames /
- (float)data->audio_format.sample_rate);
-
- data->initialized = true;
-
- return true;
-}
-
-FLAC__StreamDecoderWriteStatus
-flac_common_write(struct flac_data *data, const FLAC__Frame * frame,
- const FLAC__int32 *const buf[],
- FLAC__uint64 nbytes)
-{
- enum decoder_command cmd;
- void *buffer;
- unsigned bit_rate;
-
- if (!data->initialized && !flac_got_first_frame(data, &frame->header))
- return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
-
- size_t buffer_size = frame->header.blocksize * data->frame_size;
- buffer = pcm_buffer_get(&data->buffer, buffer_size);
-
- flac_convert(buffer, frame->header.channels,
- data->audio_format.format, buf,
- 0, frame->header.blocksize);
-
- if (nbytes > 0)
- bit_rate = nbytes * 8 * frame->header.sample_rate /
- (1000 * frame->header.blocksize);
- else
- bit_rate = 0;
-
- cmd = decoder_data(data->decoder, data->input_stream,
- buffer, buffer_size,
- bit_rate);
- data->next_frame += frame->header.blocksize;
- switch (cmd) {
- case DECODE_COMMAND_NONE:
- case DECODE_COMMAND_START:
- break;
-
- case DECODE_COMMAND_STOP:
- return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
-
- case DECODE_COMMAND_SEEK:
- return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
- }
-
- return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
-}
diff --git a/src/decoder/_flac_common.h b/src/decoder/_flac_common.h
deleted file mode 100644
index 0d90ba656..000000000
--- a/src/decoder/_flac_common.h
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * Common data structures and functions used by FLAC and OggFLAC
- */
-
-#ifndef MPD_FLAC_COMMON_H
-#define MPD_FLAC_COMMON_H
-
-#include "decoder_api.h"
-#include "pcm_buffer.h"
-
-#include <glib.h>
-
-#include <FLAC/stream_decoder.h>
-#include <FLAC/metadata.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "flac"
-
-struct flac_data {
- struct pcm_buffer buffer;
-
- /**
- * The size of one frame in the output buffer.
- */
- unsigned frame_size;
-
- /**
- * Has decoder_initialized() been called yet?
- */
- bool initialized;
-
- /**
- * Does the FLAC file contain an unsupported audio format?
- */
- bool unsupported;
-
- /**
- * The validated audio format of the FLAC file. This
- * attribute is defined if "initialized" is true.
- */
- struct audio_format audio_format;
-
- /**
- * The total number of frames in this song. The decoder
- * plugin may initialize this attribute to override the value
- * provided by libFLAC (e.g. for sub songs from a CUE sheet).
- */
- FLAC__uint64 total_frames;
-
- /**
- * The number of the first frame in this song. This is only
- * non-zero if playing sub songs from a CUE sheet.
- */
- FLAC__uint64 first_frame;
-
- /**
- * The number of the next frame which is going to be decoded.
- */
- FLAC__uint64 next_frame;
-
- FLAC__uint64 position;
- struct decoder *decoder;
- struct input_stream *input_stream;
- struct tag *tag;
-};
-
-/* initializes a given FlacData struct */
-void
-flac_data_init(struct flac_data *data, struct decoder * decoder,
- struct input_stream *input_stream);
-
-void
-flac_data_deinit(struct flac_data *data);
-
-void flac_metadata_common_cb(const FLAC__StreamMetadata * block,
- struct flac_data *data);
-
-void flac_error_common_cb(FLAC__StreamDecoderErrorStatus status,
- struct flac_data *data);
-
-FLAC__StreamDecoderWriteStatus
-flac_common_write(struct flac_data *data, const FLAC__Frame * frame,
- const FLAC__int32 *const buf[],
- FLAC__uint64 nbytes);
-
-#endif /* _FLAC_COMMON_H */
diff --git a/src/decoder/_ogg_common.c b/src/decoder/_ogg_common.c
deleted file mode 100644
index 09d2712da..000000000
--- a/src/decoder/_ogg_common.c
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * Common functions used for Ogg data streams (Ogg-Vorbis and OggFLAC)
- */
-
-#include "config.h"
-#include "_ogg_common.h"
-
-ogg_stream_type ogg_stream_type_detect(struct input_stream *inStream)
-{
- /* oggflac detection based on code in ogg123 and this post
- * http://lists.xiph.org/pipermail/flac/2004-December/000393.html
- * ogg123 trunk still doesn't have this patch as of June 2005 */
- unsigned char buf[41];
- size_t r;
-
- r = decoder_read(NULL, inStream, buf, sizeof(buf));
- if (r < sizeof(buf) || memcmp(buf, "OggS", 4) != 0)
- return VORBIS;
-
- if ((memcmp(buf + 29, "FLAC", 4) == 0 &&
- memcmp(buf + 37, "fLaC", 4) == 0) ||
- memcmp(buf + 28, "FLAC", 4) == 0 ||
- memcmp(buf + 28, "fLaC", 4) == 0)
- return FLAC;
-
- return VORBIS;
-}
diff --git a/src/decoder/_ogg_common.h b/src/decoder/_ogg_common.h
deleted file mode 100644
index 85e4ebba6..000000000
--- a/src/decoder/_ogg_common.h
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * Common functions used for Ogg data streams (Ogg-Vorbis and OggFLAC)
- */
-
-#ifndef MPD_OGG_COMMON_H
-#define MPD_OGG_COMMON_H
-
-#include "decoder_api.h"
-
-typedef enum _ogg_stream_type { VORBIS, FLAC } ogg_stream_type;
-
-ogg_stream_type ogg_stream_type_detect(struct input_stream *inStream);
-
-#endif /* _OGG_COMMON_H */
diff --git a/src/decoder/audiofile_decoder_plugin.c b/src/decoder/audiofile_decoder_plugin.c
deleted file mode 100644
index b344795e7..000000000
--- a/src/decoder/audiofile_decoder_plugin.c
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "decoder_api.h"
-#include "audio_check.h"
-#include "tag_handler.h"
-
-#include <audiofile.h>
-#include <af_vfs.h>
-#include <assert.h>
-#include <glib.h>
-#include <stdio.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "audiofile"
-
-/* pick 1020 since its devisible for 8,16,24, and 32-bit audio */
-#define CHUNK_SIZE 1020
-
-static int audiofile_get_duration(const char *file)
-{
- int total_time;
- AFfilehandle af_fp = afOpenFile(file, "r", NULL);
- if (af_fp == AF_NULL_FILEHANDLE) {
- return -1;
- }
- total_time = (int)
- ((double)afGetFrameCount(af_fp, AF_DEFAULT_TRACK)
- / afGetRate(af_fp, AF_DEFAULT_TRACK));
- afCloseFile(af_fp);
- return total_time;
-}
-
-static ssize_t
-audiofile_file_read(AFvirtualfile *vfile, void *data, size_t length)
-{
- struct input_stream *is = (struct input_stream *) vfile->closure;
- GError *error = NULL;
- size_t nbytes;
-
- nbytes = input_stream_lock_read(is, data, length, &error);
- if (nbytes == 0 && error != NULL) {
- g_warning("%s", error->message);
- g_error_free(error);
- return -1;
- }
-
- return nbytes;
-}
-
-static AFfileoffset
-audiofile_file_length(AFvirtualfile *vfile)
-{
- struct input_stream *is = (struct input_stream *) vfile->closure;
- return is->size;
-}
-
-static AFfileoffset
-audiofile_file_tell(AFvirtualfile *vfile)
-{
- struct input_stream *is = (struct input_stream *) vfile->closure;
- return is->offset;
-}
-
-static void
-audiofile_file_destroy(AFvirtualfile *vfile)
-{
- assert(vfile->closure != NULL);
-
- vfile->closure = NULL;
-}
-
-static AFfileoffset
-audiofile_file_seek(AFvirtualfile *vfile, AFfileoffset offset, int is_relative)
-{
- struct input_stream *is = (struct input_stream *) vfile->closure;
- int whence = (is_relative ? SEEK_CUR : SEEK_SET);
- if (input_stream_lock_seek(is, offset, whence, NULL)) {
- return is->offset;
- } else {
- return -1;
- }
-}
-
-static AFvirtualfile *
-setup_virtual_fops(struct input_stream *stream)
-{
- AFvirtualfile *vf = g_malloc(sizeof(AFvirtualfile));
- vf->closure = stream;
- vf->write = NULL;
- vf->read = audiofile_file_read;
- vf->length = audiofile_file_length;
- vf->destroy = audiofile_file_destroy;
- vf->seek = audiofile_file_seek;
- vf->tell = audiofile_file_tell;
- return vf;
-}
-
-static enum sample_format
-audiofile_bits_to_sample_format(int bits)
-{
- switch (bits) {
- case 8:
- return SAMPLE_FORMAT_S8;
-
- case 16:
- return SAMPLE_FORMAT_S16;
-
- case 24:
- return SAMPLE_FORMAT_S24_P32;
-
- case 32:
- return SAMPLE_FORMAT_S32;
- }
-
- return SAMPLE_FORMAT_UNDEFINED;
-}
-
-static enum sample_format
-audiofile_setup_sample_format(AFfilehandle af_fp)
-{
- int fs, bits;
-
- afGetSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits);
- if (!audio_valid_sample_format(audiofile_bits_to_sample_format(bits))) {
- g_debug("input file has %d bit samples, converting to 16",
- bits);
- bits = 16;
- }
-
- afSetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK,
- AF_SAMPFMT_TWOSCOMP, bits);
- afGetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits);
-
- return audiofile_bits_to_sample_format(bits);
-}
-
-static void
-audiofile_stream_decode(struct decoder *decoder, struct input_stream *is)
-{
- GError *error = NULL;
- AFvirtualfile *vf;
- int fs, frame_count;
- AFfilehandle af_fp;
- struct audio_format audio_format;
- float total_time;
- uint16_t bit_rate;
- int ret;
- char chunk[CHUNK_SIZE];
- enum decoder_command cmd;
-
- if (!is->seekable) {
- g_warning("not seekable");
- return;
- }
-
- vf = setup_virtual_fops(is);
-
- af_fp = afOpenVirtualFile(vf, "r", NULL);
- if (af_fp == AF_NULL_FILEHANDLE) {
- g_warning("failed to input stream\n");
- return;
- }
-
- if (!audio_format_init_checked(&audio_format,
- afGetRate(af_fp, AF_DEFAULT_TRACK),
- audiofile_setup_sample_format(af_fp),
- afGetVirtualChannels(af_fp, AF_DEFAULT_TRACK),
- &error)) {
- g_warning("%s", error->message);
- g_error_free(error);
- afCloseFile(af_fp);
- return;
- }
-
- frame_count = afGetFrameCount(af_fp, AF_DEFAULT_TRACK);
-
- total_time = ((float)frame_count / (float)audio_format.sample_rate);
-
- bit_rate = (uint16_t)(is->size * 8.0 / total_time / 1000.0 + 0.5);
-
- fs = (int)afGetVirtualFrameSize(af_fp, AF_DEFAULT_TRACK, 1);
-
- decoder_initialized(decoder, &audio_format, true, total_time);
-
- do {
- ret = afReadFrames(af_fp, AF_DEFAULT_TRACK, chunk,
- CHUNK_SIZE / fs);
- if (ret <= 0)
- break;
-
- cmd = decoder_data(decoder, NULL,
- chunk, ret * fs,
- bit_rate);
-
- if (cmd == DECODE_COMMAND_SEEK) {
- AFframecount frame = decoder_seek_where(decoder) *
- audio_format.sample_rate;
- afSeekFrame(af_fp, AF_DEFAULT_TRACK, frame);
-
- decoder_command_finished(decoder);
- cmd = DECODE_COMMAND_NONE;
- }
- } while (cmd == DECODE_COMMAND_NONE);
-
- afCloseFile(af_fp);
-}
-
-static bool
-audiofile_scan_file(const char *file,
- const struct tag_handler *handler, void *handler_ctx)
-{
- int total_time = audiofile_get_duration(file);
-
- if (total_time < 0) {
- g_debug("Failed to get total song time from: %s\n",
- file);
- return false;
- }
-
- tag_handler_invoke_duration(handler, handler_ctx, total_time);
- return true;
-}
-
-static const char *const audiofile_suffixes[] = {
- "wav", "au", "aiff", "aif", NULL
-};
-
-static const char *const audiofile_mime_types[] = {
- "audio/x-wav",
- "audio/x-aiff",
- NULL
-};
-
-const struct decoder_plugin audiofile_decoder_plugin = {
- .name = "audiofile",
- .stream_decode = audiofile_stream_decode,
- .scan_file = audiofile_scan_file,
- .suffixes = audiofile_suffixes,
- .mime_types = audiofile_mime_types,
-};
diff --git a/src/decoder/dsdiff_decoder_plugin.c b/src/decoder/dsdiff_decoder_plugin.c
deleted file mode 100644
index 84471fb3a..000000000
--- a/src/decoder/dsdiff_decoder_plugin.c
+++ /dev/null
@@ -1,397 +0,0 @@
-/*
- * 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.
- */
-
-/* \file
- *
- * This plugin decodes DSDIFF data (SACD) embedded in DFF files.
- * The DFF code was modeled after the specification found here:
- * http://www.sonicstudio.com/pdf/dsd/DSDIFF_1.5_Spec.pdf
- *
- * All functions common to both DSD decoders have been moved to dsdlib
- */
-
-#include "config.h"
-#include "dsdiff_decoder_plugin.h"
-#include "decoder_api.h"
-#include "audio_check.h"
-#include "util/bit_reverse.h"
-#include "tag_handler.h"
-#include "dsdlib.h"
-#include "tag_handler.h"
-
-#include <unistd.h>
-#include <stdio.h> /* for SEEK_SET, SEEK_CUR */
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "dsdiff"
-
-struct dsdiff_header {
- struct dsdlib_id id;
- uint32_t size_high, size_low;
- struct dsdlib_id format;
-};
-
-struct dsdiff_chunk_header {
- struct dsdlib_id id;
- uint32_t size_high, size_low;
-};
-
-struct dsdiff_metadata {
- unsigned sample_rate, channels;
- bool bitreverse;
- uint64_t chunk_size;
-};
-
-static bool lsbitfirst;
-
-static bool
-dsdiff_init(const struct config_param *param)
-{
- lsbitfirst = config_get_block_bool(param, "lsbitfirst", false);
- return true;
-}
-
-/**
- * Read the "size" attribute from the specified header, converting it
- * to the host byte order if needed.
- */
-G_GNUC_CONST
-static uint64_t
-dsdiff_chunk_size(const struct dsdiff_chunk_header *header)
-{
- return (((uint64_t)GUINT32_FROM_BE(header->size_high)) << 32) |
- ((uint64_t)GUINT32_FROM_BE(header->size_low));
-}
-
-static bool
-dsdiff_read_id(struct decoder *decoder, struct input_stream *is,
- struct dsdlib_id *id)
-{
- return dsdlib_read(decoder, is, id, sizeof(*id));
-}
-
-static bool
-dsdiff_read_chunk_header(struct decoder *decoder, struct input_stream *is,
- struct dsdiff_chunk_header *header)
-{
- return dsdlib_read(decoder, is, header, sizeof(*header));
-}
-
-static bool
-dsdiff_read_payload(struct decoder *decoder, struct input_stream *is,
- const struct dsdiff_chunk_header *header,
- void *data, size_t length)
-{
- uint64_t size = dsdiff_chunk_size(header);
- if (size != (uint64_t)length)
- return false;
-
- size_t nbytes = decoder_read(decoder, is, data, length);
- return nbytes == length;
-}
-
-/**
- * Read and parse a "SND" chunk inside "PROP".
- */
-static bool
-dsdiff_read_prop_snd(struct decoder *decoder, struct input_stream *is,
- struct dsdiff_metadata *metadata,
- goffset end_offset)
-{
- struct dsdiff_chunk_header header;
- while ((goffset)(is->offset + sizeof(header)) <= end_offset) {
- if (!dsdiff_read_chunk_header(decoder, is, &header))
- return false;
-
- goffset chunk_end_offset =
- is->offset + dsdiff_chunk_size(&header);
- if (chunk_end_offset > end_offset)
- return false;
-
- if (dsdlib_id_equals(&header.id, "FS ")) {
- uint32_t sample_rate;
- if (!dsdiff_read_payload(decoder, is, &header,
- &sample_rate,
- sizeof(sample_rate)))
- return false;
-
- metadata->sample_rate = GUINT32_FROM_BE(sample_rate);
- } else if (dsdlib_id_equals(&header.id, "CHNL")) {
- uint16_t channels;
- if (dsdiff_chunk_size(&header) < sizeof(channels) ||
- !dsdlib_read(decoder, is,
- &channels, sizeof(channels)) ||
- !dsdlib_skip_to(decoder, is, chunk_end_offset))
- return false;
-
- metadata->channels = GUINT16_FROM_BE(channels);
- } else if (dsdlib_id_equals(&header.id, "CMPR")) {
- struct dsdlib_id type;
- if (dsdiff_chunk_size(&header) < sizeof(type) ||
- !dsdlib_read(decoder, is,
- &type, sizeof(type)) ||
- !dsdlib_skip_to(decoder, is, chunk_end_offset))
- return false;
-
- if (!dsdlib_id_equals(&type, "DSD "))
- /* only uncompressed DSD audio data
- is implemented */
- return false;
- } else {
- /* ignore unknown chunk */
-
- if (!dsdlib_skip_to(decoder, is, chunk_end_offset))
- return false;
- }
- }
-
- return is->offset == end_offset;
-}
-
-/**
- * Read and parse a "PROP" chunk.
- */
-static bool
-dsdiff_read_prop(struct decoder *decoder, struct input_stream *is,
- struct dsdiff_metadata *metadata,
- const struct dsdiff_chunk_header *prop_header)
-{
- uint64_t prop_size = dsdiff_chunk_size(prop_header);
- goffset end_offset = is->offset + prop_size;
-
- struct dsdlib_id prop_id;
- if (prop_size < sizeof(prop_id) ||
- !dsdiff_read_id(decoder, is, &prop_id))
- return false;
-
- if (dsdlib_id_equals(&prop_id, "SND "))
- return dsdiff_read_prop_snd(decoder, is, metadata, end_offset);
- else
- /* ignore unknown PROP chunk */
- return dsdlib_skip_to(decoder, is, end_offset);
-}
-
-/**
- * Read and parse all metadata chunks at the beginning. Stop when the
- * first "DSD" chunk is seen, and return its header in the
- * "chunk_header" parameter.
- */
-static bool
-dsdiff_read_metadata(struct decoder *decoder, struct input_stream *is,
- struct dsdiff_metadata *metadata,
- struct dsdiff_chunk_header *chunk_header)
-{
- struct dsdiff_header header;
- if (!dsdlib_read(decoder, is, &header, sizeof(header)) ||
- !dsdlib_id_equals(&header.id, "FRM8") ||
- !dsdlib_id_equals(&header.format, "DSD "))
- return false;
-
- while (true) {
- if (!dsdiff_read_chunk_header(decoder, is,
- chunk_header))
- return false;
-
- if (dsdlib_id_equals(&chunk_header->id, "PROP")) {
- if (!dsdiff_read_prop(decoder, is, metadata,
- chunk_header))
- return false;
- } else if (dsdlib_id_equals(&chunk_header->id, "DSD ")) {
- uint64_t chunk_size;
- chunk_size = dsdiff_chunk_size(chunk_header);
- metadata->chunk_size = chunk_size;
- return true;
- } else {
- /* ignore unknown chunk */
- uint64_t chunk_size;
- chunk_size = dsdiff_chunk_size(chunk_header);
- goffset chunk_end_offset = is->offset + chunk_size;
-
- if (!dsdlib_skip_to(decoder, is, chunk_end_offset))
- return false;
- }
- }
-}
-
-static void
-bit_reverse_buffer(uint8_t *p, uint8_t *end)
-{
- for (; p < end; ++p)
- *p = bit_reverse(*p);
-}
-
-/**
- * Decode one "DSD" chunk.
- */
-static bool
-dsdiff_decode_chunk(struct decoder *decoder, struct input_stream *is,
- unsigned channels,
- uint64_t chunk_size)
-{
- uint8_t buffer[8192];
-
- const size_t sample_size = sizeof(buffer[0]);
- const size_t frame_size = channels * sample_size;
- const unsigned buffer_frames = sizeof(buffer) / frame_size;
- const unsigned buffer_samples = buffer_frames * frame_size;
- const size_t buffer_size = buffer_samples * sample_size;
-
- while (chunk_size > 0) {
- /* see how much aligned data from the remaining chunk
- fits into the local buffer */
- unsigned now_frames = buffer_frames;
- size_t now_size = buffer_size;
- if (chunk_size < (uint64_t)now_size) {
- now_frames = (unsigned)chunk_size / frame_size;
- now_size = now_frames * frame_size;
- }
-
- size_t nbytes = decoder_read(decoder, is, buffer, now_size);
- if (nbytes != now_size)
- return false;
-
- chunk_size -= nbytes;
-
- if (lsbitfirst)
- bit_reverse_buffer(buffer, buffer + nbytes);
-
- enum decoder_command cmd =
- decoder_data(decoder, is, buffer, nbytes, 0);
- switch (cmd) {
- case DECODE_COMMAND_NONE:
- break;
-
- case DECODE_COMMAND_START:
- case DECODE_COMMAND_STOP:
- return false;
-
- case DECODE_COMMAND_SEEK:
-
- /* Not implemented yet */
- decoder_seek_error(decoder);
- break;
- }
- }
- return dsdlib_skip(decoder, is, chunk_size);
-}
-
-static void
-dsdiff_stream_decode(struct decoder *decoder, struct input_stream *is)
-{
- struct dsdiff_metadata metadata = {
- .sample_rate = 0,
- .channels = 0,
- };
-
- struct dsdiff_chunk_header chunk_header;
- /* check if it is is a proper DFF file */
- if (!dsdiff_read_metadata(decoder, is, &metadata, &chunk_header))
- return;
-
- GError *error = NULL;
- struct audio_format audio_format;
- if (!audio_format_init_checked(&audio_format, metadata.sample_rate / 8,
- SAMPLE_FORMAT_DSD,
- metadata.channels, &error)) {
- g_warning("%s", error->message);
- g_error_free(error);
- return;
- }
-
- /* calculate song time from DSD chunk size and sample frequency */
- uint64_t chunk_size = metadata.chunk_size;
- float songtime = ((chunk_size / metadata.channels) * 8) /
- (float) metadata.sample_rate;
-
- /* success: file was recognized */
- decoder_initialized(decoder, &audio_format, false, songtime);
-
- /* every iteration of the following loop decodes one "DSD"
- chunk from a DFF file */
-
- while (true) {
- chunk_size = dsdiff_chunk_size(&chunk_header);
-
- if (dsdlib_id_equals(&chunk_header.id, "DSD ")) {
- if (!dsdiff_decode_chunk(decoder, is,
- metadata.channels,
- chunk_size))
- break;
- } else {
- /* ignore other chunks */
- if (!dsdlib_skip(decoder, is, chunk_size))
- break;
- }
-
- /* read next chunk header; the first one was read by
- dsdiff_read_metadata() */
- if (!dsdiff_read_chunk_header(decoder,
- is, &chunk_header))
- break;
- }
-}
-
-static bool
-dsdiff_scan_stream(struct input_stream *is,
- G_GNUC_UNUSED const struct tag_handler *handler,
- G_GNUC_UNUSED void *handler_ctx)
-{
- struct dsdiff_metadata metadata = {
- .sample_rate = 0,
- .channels = 0,
- };
-
- struct dsdiff_chunk_header chunk_header;
- /* First check for DFF metadata */
- if (!dsdiff_read_metadata(NULL, is, &metadata, &chunk_header))
- return false;
-
- struct audio_format audio_format;
- if (!audio_format_init_checked(&audio_format, metadata.sample_rate / 8,
- SAMPLE_FORMAT_DSD,
- metadata.channels, NULL))
- /* refuse to parse files which we cannot play anyway */
- return false;
-
- /* calculate song time and add as tag */
- unsigned songtime = ((metadata.chunk_size / metadata.channels) * 8) /
- metadata.sample_rate;
- tag_handler_invoke_duration(handler, handler_ctx, songtime);
-
- return true;
-}
-
-static const char *const dsdiff_suffixes[] = {
- "dff",
- NULL
-};
-
-static const char *const dsdiff_mime_types[] = {
- "application/x-dff",
- NULL
-};
-
-const struct decoder_plugin dsdiff_decoder_plugin = {
- .name = "dsdiff",
- .init = dsdiff_init,
- .stream_decode = dsdiff_stream_decode,
- .scan_stream = dsdiff_scan_stream,
- .suffixes = dsdiff_suffixes,
- .mime_types = dsdiff_mime_types,
-};
diff --git a/src/decoder/dsdiff_decoder_plugin.h b/src/decoder/dsdiff_decoder_plugin.h
deleted file mode 100644
index 452f9050b..000000000
--- a/src/decoder/dsdiff_decoder_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef MPD_DECODER_DSDIFF_H
-#define MPD_DECODER_DSDIFF_H
-
-extern const struct decoder_plugin dsdiff_decoder_plugin;
-
-#endif
diff --git a/src/decoder/dsdlib.c b/src/decoder/dsdlib.c
deleted file mode 100644
index 3df9497c4..000000000
--- a/src/decoder/dsdlib.c
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 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.
- */
-
-/* \file
- *
- * This file contains functions used by the DSF and DSDIFF decoders.
- *
- */
-
-#include "config.h"
-#include "dsf_decoder_plugin.h"
-#include "decoder_api.h"
-#include "util/bit_reverse.h"
-#include "dsdlib.h"
-#include "dsdiff_decoder_plugin.h"
-
-#include <unistd.h>
-#include <stdio.h> /* for SEEK_SET, SEEK_CUR */
-
-bool
-dsdlib_id_equals(const struct dsdlib_id *id, const char *s)
-{
- assert(id != NULL);
- assert(s != NULL);
- assert(strlen(s) == sizeof(id->value));
-
- return memcmp(id->value, s, sizeof(id->value)) == 0;
-}
-
-bool
-dsdlib_read(struct decoder *decoder, struct input_stream *is,
- void *data, size_t length)
-{
- size_t nbytes = decoder_read(decoder, is, data, length);
- return nbytes == length;
-}
-
-/**
- * Skip the #input_stream to the specified offset.
- */
-bool
-dsdlib_skip_to(struct decoder *decoder, struct input_stream *is,
- goffset offset)
-{
- if (is->seekable)
- return input_stream_seek(is, offset, SEEK_SET, NULL);
-
- if (is->offset > offset)
- return false;
-
- char buffer[8192];
- while (is->offset < offset) {
- size_t length = sizeof(buffer);
- if (offset - is->offset < (goffset)length)
- length = offset - is->offset;
-
- size_t nbytes = decoder_read(decoder, is, buffer, length);
- if (nbytes == 0)
- return false;
- }
-
- assert(is->offset == offset);
- return true;
-}
-
-/**
- * Skip some bytes from the #input_stream.
- */
-bool
-dsdlib_skip(struct decoder *decoder, struct input_stream *is,
- goffset delta)
-{
- assert(delta >= 0);
-
- if (delta == 0)
- return true;
-
- if (is->seekable)
- return input_stream_seek(is, delta, SEEK_CUR, NULL);
-
- char buffer[8192];
- while (delta > 0) {
- size_t length = sizeof(buffer);
- if ((goffset)length > delta)
- length = delta;
-
- size_t nbytes = decoder_read(decoder, is, buffer, length);
- if (nbytes == 0)
- return false;
-
- delta -= nbytes;
- }
-
- return true;
-}
-
diff --git a/src/decoder/dsdlib.h b/src/decoder/dsdlib.h
deleted file mode 100644
index d9675f5fe..000000000
--- a/src/decoder/dsdlib.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 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.
- */
-
-#ifndef MPD_DECODER_DSDLIB_H
-#define MPD_DECODER_DSDLIB_H
-
-struct dsdlib_id {
- char value[4];
-};
-
-bool
-dsdlib_id_equals(const struct dsdlib_id *id, const char *s);
-
-bool
-dsdlib_read(struct decoder *decoder, struct input_stream *is,
- void *data, size_t length);
-
-bool
-dsdlib_skip_to(struct decoder *decoder, struct input_stream *is,
- goffset offset);
-
-bool
-dsdlib_skip(struct decoder *decoder, struct input_stream *is,
- goffset delta);
-
-#endif
diff --git a/src/decoder/dsf_decoder_plugin.c b/src/decoder/dsf_decoder_plugin.c
deleted file mode 100644
index c0107eb30..000000000
--- a/src/decoder/dsf_decoder_plugin.c
+++ /dev/null
@@ -1,338 +0,0 @@
-/*
- * Copyright (C) 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.
- */
-
-/* \file
- *
- * This plugin decodes DSDIFF data (SACD) embedded in DSF files.
- *
- * The DSF code was created using the specification found here:
- * http://dsd-guide.com/sonys-dsf-file-format-spec
- *
- * All functions common to both DSD decoders have been moved to dsdlib
- */
-
-#include "config.h"
-#include "dsf_decoder_plugin.h"
-#include "decoder_api.h"
-#include "audio_check.h"
-#include "util/bit_reverse.h"
-#include "dsdlib.h"
-#include "tag_handler.h"
-
-#include <unistd.h>
-#include <stdio.h> /* for SEEK_SET, SEEK_CUR */
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "dsf"
-
-struct dsf_metadata {
- unsigned sample_rate, channels;
- bool bitreverse;
- uint64_t chunk_size;
-};
-
-struct dsf_header {
- /** DSF header id: "DSD " */
- struct dsdlib_id id;
- /** DSD chunk size, including id = 28 */
- uint32_t size_low, size_high;
- /** total file size */
- uint32_t fsize_low, fsize_high;
- /** pointer to id3v2 metadata, should be at the end of the file */
- uint32_t pmeta_low, pmeta_high;
-};
-/** DSF file fmt chunk */
-struct dsf_fmt_chunk {
-
- /** id: "fmt " */
- struct dsdlib_id id;
- /** fmt chunk size, including id, normally 52 */
- uint32_t size_low, size_high;
- /** version of this format = 1 */
- uint32_t version;
- /** 0: DSD raw */
- uint32_t formatid;
- /** channel type, 1 = mono, 2 = stereo, 3 = 3 channels, etc */
- uint32_t channeltype;
- /** Channel number, 1 = mono, 2 = stereo, ... 6 = 6 channels */
- uint32_t channelnum;
- /** sample frequency: 2822400, 5644800 */
- uint32_t sample_freq;
- /** bits per sample 1 or 8 */
- uint32_t bitssample;
- /** Sample count per channel in bytes */
- uint32_t scnt_low, scnt_high;
- /** block size per channel = 4096 */
- uint32_t block_size;
- /** reserved, should be all zero */
- uint32_t reserved;
-};
-
-struct dsf_data_chunk {
- struct dsdlib_id id;
- /** "data" chunk size, includes header (id+size) */
- uint32_t size_low, size_high;
-};
-
-/**
- * Read and parse all needed metadata chunks for DSF files.
- */
-static bool
-dsf_read_metadata(struct decoder *decoder, struct input_stream *is,
- struct dsf_metadata *metadata)
-{
- uint64_t chunk_size;
- struct dsf_header dsf_header;
- if (!dsdlib_read(decoder, is, &dsf_header, sizeof(dsf_header)) ||
- !dsdlib_id_equals(&dsf_header.id, "DSD "))
- return false;
-
- chunk_size = (((uint64_t)GUINT32_FROM_LE(dsf_header.size_high)) << 32) |
- ((uint64_t)GUINT32_FROM_LE(dsf_header.size_low));
-
- if (sizeof(dsf_header) != chunk_size)
- return false;
-
- /* read the 'fmt ' chunk of the DSF file */
- struct dsf_fmt_chunk dsf_fmt_chunk;
- if (!dsdlib_read(decoder, is, &dsf_fmt_chunk, sizeof(dsf_fmt_chunk)) ||
- !dsdlib_id_equals(&dsf_fmt_chunk.id, "fmt "))
- return false;
-
- uint64_t fmt_chunk_size;
- fmt_chunk_size = (((uint64_t)GUINT32_FROM_LE(dsf_fmt_chunk.size_high)) << 32) |
- ((uint64_t)GUINT32_FROM_LE(dsf_fmt_chunk.size_low));
-
- if (fmt_chunk_size != sizeof(dsf_fmt_chunk))
- return false;
-
- uint32_t samplefreq = (uint32_t)GUINT32_FROM_LE(dsf_fmt_chunk.sample_freq);
-
- /* for now, only support version 1 of the standard, DSD raw stereo
- files with a sample freq of 2822400 Hz */
-
- if (dsf_fmt_chunk.version != 1 || dsf_fmt_chunk.formatid != 0
- || dsf_fmt_chunk.channeltype != 2
- || dsf_fmt_chunk.channelnum != 2
- || samplefreq != 2822400)
- return false;
-
- uint32_t chblksize = (uint32_t)GUINT32_FROM_LE(dsf_fmt_chunk.block_size);
- /* according to the spec block size should always be 4096 */
- if (chblksize != 4096)
- return false;
-
- /* read the 'data' chunk of the DSF file */
- struct dsf_data_chunk data_chunk;
- if (!dsdlib_read(decoder, is, &data_chunk, sizeof(data_chunk)) ||
- !dsdlib_id_equals(&data_chunk.id, "data"))
- return false;
-
- /* data size of DSF files are padded to multiple of 4096,
- we use the actual data size as chunk size */
-
- uint64_t data_size;
- data_size = (((uint64_t)GUINT32_FROM_LE(data_chunk.size_high)) << 32) |
- ((uint64_t)GUINT32_FROM_LE(data_chunk.size_low));
- data_size -= sizeof(data_chunk);
-
- metadata->chunk_size = data_size;
- metadata->channels = (unsigned) dsf_fmt_chunk.channelnum;
- metadata->sample_rate = samplefreq;
-
- /* check bits per sample format, determine if bitreverse is needed */
- metadata->bitreverse = dsf_fmt_chunk.bitssample == 1;
- return true;
-}
-
-static void
-bit_reverse_buffer(uint8_t *p, uint8_t *end)
-{
- for (; p < end; ++p)
- *p = bit_reverse(*p);
-}
-
-/**
- * DSF data is build up of alternating 4096 blocks of DSD samples for left and
- * right. Convert the buffer holding 1 block of 4096 DSD left samples and 1
- * block of 4096 DSD right samples to 8k of samples in normal PCM left/right
- * order.
- */
-static void
-dsf_to_pcm_order(uint8_t *dest, uint8_t *scratch, size_t nrbytes)
-{
- for (unsigned i = 0, j = 0; i < (unsigned)nrbytes; i += 2) {
- scratch[i] = *(dest+j);
- j++;
- }
-
- for (unsigned i = 1, j = 0; i < (unsigned) nrbytes; i += 2) {
- scratch[i] = *(dest+4096+j);
- j++;
- }
-
- for (unsigned i = 0; i < (unsigned)nrbytes; i++) {
- *dest = scratch[i];
- dest++;
- }
-}
-
-/**
- * Decode one complete DSF 'data' chunk i.e. a complete song
- */
-static bool
-dsf_decode_chunk(struct decoder *decoder, struct input_stream *is,
- unsigned channels,
- uint64_t chunk_size,
- bool bitreverse)
-{
- uint8_t buffer[8192];
-
- /* scratch buffer for DSF samples to convert to the needed
- normal left/right regime of samples */
- uint8_t dsf_scratch_buffer[8192];
-
- const size_t sample_size = sizeof(buffer[0]);
- const size_t frame_size = channels * sample_size;
- const unsigned buffer_frames = sizeof(buffer) / frame_size;
- const unsigned buffer_samples = buffer_frames * frame_size;
- const size_t buffer_size = buffer_samples * sample_size;
-
- while (chunk_size > 0) {
- /* see how much aligned data from the remaining chunk
- fits into the local buffer */
- unsigned now_frames = buffer_frames;
- size_t now_size = buffer_size;
- if (chunk_size < (uint64_t)now_size) {
- now_frames = (unsigned)chunk_size / frame_size;
- now_size = now_frames * frame_size;
- }
-
- size_t nbytes = decoder_read(decoder, is, buffer, now_size);
- if (nbytes != now_size)
- return false;
-
- chunk_size -= nbytes;
-
- if (bitreverse)
- bit_reverse_buffer(buffer, buffer + nbytes);
-
- dsf_to_pcm_order(buffer, dsf_scratch_buffer, nbytes);
-
- enum decoder_command cmd =
- decoder_data(decoder, is, buffer, nbytes, 0);
- switch (cmd) {
- case DECODE_COMMAND_NONE:
- break;
-
- case DECODE_COMMAND_START:
- case DECODE_COMMAND_STOP:
- return false;
-
- case DECODE_COMMAND_SEEK:
-
- /* not implemented yet */
- decoder_seek_error(decoder);
- break;
- }
- }
- return dsdlib_skip(decoder, is, chunk_size);
-}
-
-static void
-dsf_stream_decode(struct decoder *decoder, struct input_stream *is)
-{
- struct dsf_metadata metadata = {
- .sample_rate = 0,
- .channels = 0,
- };
-
- /* check if it is a proper DSF file */
- if (!dsf_read_metadata(decoder, is, &metadata))
- return;
-
- GError *error = NULL;
- struct audio_format audio_format;
- if (!audio_format_init_checked(&audio_format, metadata.sample_rate / 8,
- SAMPLE_FORMAT_DSD,
- metadata.channels, &error)) {
- g_warning("%s", error->message);
- g_error_free(error);
- return;
- }
- /* Calculate song time from DSD chunk size and sample frequency */
- uint64_t chunk_size = metadata.chunk_size;
- float songtime = ((chunk_size / metadata.channels) * 8) /
- (float) metadata.sample_rate;
-
- /* success: file was recognized */
- decoder_initialized(decoder, &audio_format, false, songtime);
-
- if (!dsf_decode_chunk(decoder, is, metadata.channels,
- metadata.chunk_size,
- metadata.bitreverse))
- return;
-}
-
-static bool
-dsf_scan_stream(struct input_stream *is,
- G_GNUC_UNUSED const struct tag_handler *handler,
- G_GNUC_UNUSED void *handler_ctx)
-{
- struct dsf_metadata metadata = {
- .sample_rate = 0,
- .channels = 0,
- };
-
- /* check DSF metadata */
- if (!dsf_read_metadata(NULL, is, &metadata))
- return false;
-
- struct audio_format audio_format;
- if (!audio_format_init_checked(&audio_format, metadata.sample_rate / 8,
- SAMPLE_FORMAT_DSD,
- metadata.channels, NULL))
- /* refuse to parse files which we cannot play anyway */
- return false;
-
- /* calculate song time and add as tag */
- unsigned songtime = ((metadata.chunk_size / metadata.channels) * 8) /
- metadata.sample_rate;
- tag_handler_invoke_duration(handler, handler_ctx, songtime);
-
- return true;
-}
-
-static const char *const dsf_suffixes[] = {
- "dsf",
- NULL
-};
-
-static const char *const dsf_mime_types[] = {
- "application/x-dsf",
- NULL
-};
-
-const struct decoder_plugin dsf_decoder_plugin = {
- .name = "dsf",
- .stream_decode = dsf_stream_decode,
- .scan_stream = dsf_scan_stream,
- .suffixes = dsf_suffixes,
- .mime_types = dsf_mime_types,
-};
diff --git a/src/decoder/dsf_decoder_plugin.h b/src/decoder/dsf_decoder_plugin.h
deleted file mode 100644
index 401d3fed7..000000000
--- a/src/decoder/dsf_decoder_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 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.
- */
-
-#ifndef MPD_DECODER_DSF_H
-#define MPD_DECODER_DSF_H
-
-extern const struct decoder_plugin dsf_decoder_plugin;
-
-#endif
diff --git a/src/decoder/faad_decoder_plugin.c b/src/decoder/faad_decoder_plugin.c
deleted file mode 100644
index 911f033b8..000000000
--- a/src/decoder/faad_decoder_plugin.c
+++ /dev/null
@@ -1,515 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "decoder_api.h"
-#include "decoder_buffer.h"
-#include "audio_check.h"
-#include "tag_handler.h"
-
-#define AAC_MAX_CHANNELS 6
-
-#include <assert.h>
-#include <unistd.h>
-#include <faad.h>
-#include <glib.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "faad"
-
-static const unsigned adts_sample_rates[] =
- { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050,
- 16000, 12000, 11025, 8000, 7350, 0, 0, 0
-};
-
-/**
- * The GLib quark used for errors reported by this plugin.
- */
-static inline GQuark
-faad_decoder_quark(void)
-{
- return g_quark_from_static_string("faad");
-}
-
-/**
- * Check whether the buffer head is an AAC frame, and return the frame
- * length. Returns 0 if it is not a frame.
- */
-static size_t
-adts_check_frame(const unsigned char *data)
-{
- /* check syncword */
- if (!((data[0] == 0xFF) && ((data[1] & 0xF6) == 0xF0)))
- return 0;
-
- return (((unsigned int)data[3] & 0x3) << 11) |
- (((unsigned int)data[4]) << 3) |
- (data[5] >> 5);
-}
-
-/**
- * Find the next AAC frame in the buffer. Returns 0 if no frame is
- * found or if not enough data is available.
- */
-static size_t
-adts_find_frame(struct decoder_buffer *buffer)
-{
- const unsigned char *data, *p;
- size_t length, frame_length;
- bool ret;
-
- while (true) {
- data = decoder_buffer_read(buffer, &length);
- if (data == NULL || length < 8) {
- /* not enough data yet */
- ret = decoder_buffer_fill(buffer);
- if (!ret)
- /* failed */
- return 0;
-
- continue;
- }
-
- /* find the 0xff marker */
- p = memchr(data, 0xff, length);
- if (p == NULL) {
- /* no marker - discard the buffer */
- decoder_buffer_consume(buffer, length);
- continue;
- }
-
- if (p > data) {
- /* discard data before 0xff */
- decoder_buffer_consume(buffer, p - data);
- continue;
- }
-
- /* is it a frame? */
- frame_length = adts_check_frame(data);
- if (frame_length == 0) {
- /* it's just some random 0xff byte; discard it
- and continue searching */
- decoder_buffer_consume(buffer, 1);
- continue;
- }
-
- if (length < frame_length) {
- /* available buffer size is smaller than the
- frame will be - attempt to read more
- data */
- ret = decoder_buffer_fill(buffer);
- if (!ret) {
- /* not enough data; discard this frame
- to prevent a possible buffer
- overflow */
- data = decoder_buffer_read(buffer, &length);
- if (data != NULL)
- decoder_buffer_consume(buffer, length);
- }
-
- continue;
- }
-
- /* found a full frame! */
- return frame_length;
- }
-}
-
-static float
-adts_song_duration(struct decoder_buffer *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);
- if (frame_length == 0)
- break;
-
-
- if (frames == 0) {
- const unsigned char *data;
- size_t buffer_length;
-
- data = decoder_buffer_read(buffer, &buffer_length);
- assert(data != NULL);
- assert(frame_length <= buffer_length);
-
- sample_rate = adts_sample_rates[(data[2] & 0x3c) >> 2];
- }
-
- decoder_buffer_consume(buffer, frame_length);
- }
-
- frames_per_second = (float)sample_rate / 1024.0;
- if (frames_per_second <= 0)
- return -1;
-
- return (float)frames / frames_per_second;
-}
-
-static float
-faad_song_duration(struct decoder_buffer *buffer, struct input_stream *is)
-{
- size_t fileread;
- size_t tagsize;
- const unsigned char *data;
- size_t length;
- bool success;
-
- fileread = is->size >= 0 ? is->size : 0;
-
- decoder_buffer_fill(buffer);
- data = decoder_buffer_read(buffer, &length);
- if (data == NULL)
- return -1;
-
- tagsize = 0;
- if (length >= 10 && !memcmp(data, "ID3", 3)) {
- /* skip the ID3 tag */
-
- tagsize = (data[6] << 21) | (data[7] << 14) |
- (data[8] << 7) | (data[9] << 0);
-
- tagsize += 10;
-
- success = decoder_buffer_skip(buffer, tagsize) &&
- decoder_buffer_fill(buffer);
- if (!success)
- return -1;
-
- data = decoder_buffer_read(buffer, &length);
- if (data == NULL)
- return -1;
- }
-
- if (is->seekable && length >= 2 &&
- data[0] == 0xFF && ((data[1] & 0xF6) == 0xF0)) {
- /* obtain the duration from the ADTS header */
- float song_length = adts_song_duration(buffer);
-
- input_stream_lock_seek(is, tagsize, SEEK_SET, NULL);
-
- data = decoder_buffer_read(buffer, &length);
- if (data != NULL)
- decoder_buffer_consume(buffer, length);
- decoder_buffer_fill(buffer);
-
- return song_length;
- } else if (length >= 5 && memcmp(data, "ADIF", 4) == 0) {
- /* obtain the duration from the ADIF header */
- unsigned bit_rate;
- size_t skip_size = (data[4] & 0x80) ? 9 : 0;
-
- if (8 + skip_size > length)
- /* 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);
-
- if (fileread != 0 && bit_rate != 0)
- return fileread * 8.0 / bit_rate;
- else
- return fileread;
- } else
- return -1;
-}
-
-/**
- * Wrapper for faacDecInit() which works around some API
- * inconsistencies in libfaad.
- */
-static bool
-faad_decoder_init(faacDecHandle decoder, struct decoder_buffer *buffer,
- struct audio_format *audio_format, GError **error_r)
-{
- union {
- /* deconst hack for libfaad */
- const void *in;
- void *out;
- } u;
- size_t length;
- 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
- warnings, use this workaround. */
- unsigned long *sample_rate_p = (unsigned long *)(void *)&sample_rate;
-#else
- uint32_t *sample_rate_p = &sample_rate;
-#endif
-
- u.in = decoder_buffer_read(buffer, &length);
- if (u.in == NULL) {
- g_set_error(error_r, faad_decoder_quark(), 0,
- "Empty file");
- return false;
- }
-
- nbytes = faacDecInit(decoder, u.out,
-#ifdef HAVE_FAAD_BUFLEN_FUNCS
- length,
-#endif
- sample_rate_p, &channels);
- if (nbytes < 0) {
- g_set_error(error_r, faad_decoder_quark(), 0,
- "Not an AAC stream");
- return false;
- }
-
- decoder_buffer_consume(buffer, nbytes);
-
- return audio_format_init_checked(audio_format, sample_rate,
- SAMPLE_FORMAT_S16, channels, error_r);
-}
-
-/**
- * Wrapper for faacDecDecode() which works around some API
- * inconsistencies in libfaad.
- */
-static const void *
-faad_decoder_decode(faacDecHandle decoder, struct decoder_buffer *buffer,
- faacDecFrameInfo *frame_info)
-{
- union {
- /* deconst hack for libfaad */
- const void *in;
- void *out;
- } u;
- size_t length;
- void *result;
-
- u.in = decoder_buffer_read(buffer, &length);
- if (u.in == NULL)
- return NULL;
-
- result = faacDecDecode(decoder, frame_info,
- u.out
-#ifdef HAVE_FAAD_BUFLEN_FUNCS
- , length
-#endif
- );
-
- return result;
-}
-
-/**
- * Get a song file's total playing time in seconds, as a float.
- * Returns 0 if the duration is unknown, and a negative value if the
- * file is invalid.
- */
-static float
-faad_get_file_time_float(struct input_stream *is)
-{
- struct decoder_buffer *buffer;
- float length;
- faacDecHandle decoder;
- faacDecConfigurationPtr config;
-
- buffer = decoder_buffer_new(NULL, is,
- FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS);
- length = faad_song_duration(buffer, is);
-
- if (length < 0) {
- bool ret;
- struct audio_format audio_format;
-
- decoder = faacDecOpen();
-
- config = faacDecGetCurrentConfiguration(decoder);
- config->outputFormat = FAAD_FMT_16BIT;
- faacDecSetConfiguration(decoder, config);
-
- decoder_buffer_fill(buffer);
-
- ret = faad_decoder_init(decoder, buffer, &audio_format, NULL);
- if (ret)
- length = 0;
-
- faacDecClose(decoder);
- }
-
- decoder_buffer_free(buffer);
-
- return length;
-}
-
-/**
- * Get a song file's total playing time in seconds, as an int.
- * Returns 0 if the duration is unknown, and a negative value if the
- * file is invalid.
- */
-static int
-faad_get_file_time(struct input_stream *is)
-{
- int file_time = -1;
- float length;
-
- if ((length = faad_get_file_time_float(is)) >= 0)
- file_time = length + 0.5;
-
- return file_time;
-}
-
-static void
-faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is)
-{
- GError *error = NULL;
- float total_time = 0;
- faacDecHandle decoder;
- struct audio_format audio_format;
- faacDecConfigurationPtr config;
- bool ret;
- uint16_t bit_rate = 0;
- struct decoder_buffer *buffer;
- enum decoder_command cmd;
-
- buffer = decoder_buffer_new(mpd_decoder, is,
- FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS);
- total_time = faad_song_duration(buffer, is);
-
- /* create the libfaad decoder */
-
- decoder = faacDecOpen();
-
- config = faacDecGetCurrentConfiguration(decoder);
- config->outputFormat = FAAD_FMT_16BIT;
-#ifdef HAVE_FAACDECCONFIGURATION_DOWNMATRIX
- config->downMatrix = 1;
-#endif
-#ifdef HAVE_FAACDECCONFIGURATION_DONTUPSAMPLEIMPLICITSBR
- config->dontUpSampleImplicitSBR = 0;
-#endif
- faacDecSetConfiguration(decoder, config);
-
- while (!decoder_buffer_is_full(buffer) &&
- !input_stream_lock_eof(is) &&
- decoder_get_command(mpd_decoder) == DECODE_COMMAND_NONE) {
- adts_find_frame(buffer);
- decoder_buffer_fill(buffer);
- }
-
- /* initialize it */
-
- ret = faad_decoder_init(decoder, buffer, &audio_format, &error);
- if (!ret) {
- g_warning("%s", error->message);
- g_error_free(error);
- faacDecClose(decoder);
- return;
- }
-
- /* initialize the MPD core */
-
- decoder_initialized(mpd_decoder, &audio_format, false, total_time);
-
- /* the decoder loop */
-
- do {
- size_t frame_size;
- const void *decoded;
- faacDecFrameInfo frame_info;
-
- /* find the next frame */
-
- frame_size = adts_find_frame(buffer);
- if (frame_size == 0)
- /* end of file */
- break;
-
- /* decode it */
-
- decoded = faad_decoder_decode(decoder, buffer, &frame_info);
-
- if (frame_info.error > 0) {
- g_warning("error decoding AAC stream: %s\n",
- faacDecGetErrorMessage(frame_info.error));
- break;
- }
-
- if (frame_info.channels != audio_format.channels) {
- g_warning("channel count changed from %u to %u",
- audio_format.channels, frame_info.channels);
- break;
- }
-
-#ifdef HAVE_FAACDECFRAMEINFO_SAMPLERATE
- if (frame_info.samplerate != audio_format.sample_rate) {
- g_warning("sample rate changed from %u to %lu",
- audio_format.sample_rate,
- (unsigned long)frame_info.samplerate);
- break;
- }
-#endif
-
- decoder_buffer_consume(buffer, frame_info.bytesconsumed);
-
- /* update bit rate and position */
-
- if (frame_info.samples > 0) {
- bit_rate = frame_info.bytesconsumed * 8.0 *
- frame_info.channels * audio_format.sample_rate /
- frame_info.samples / 1000 + 0.5;
- }
-
- /* send PCM samples to MPD */
-
- cmd = decoder_data(mpd_decoder, is, decoded,
- (size_t)frame_info.samples * 2,
- bit_rate);
- } while (cmd != DECODE_COMMAND_STOP);
-
- /* cleanup */
-
- faacDecClose(decoder);
-}
-
-static bool
-faad_scan_stream(struct input_stream *is,
- const struct tag_handler *handler, void *handler_ctx)
-{
- int file_time = faad_get_file_time(is);
-
- if (file_time < 0)
- return false;
-
- tag_handler_invoke_duration(handler, handler_ctx, file_time);
- return true;
-}
-
-static const char *const faad_suffixes[] = { "aac", NULL };
-static const char *const faad_mime_types[] = {
- "audio/aac", "audio/aacp", NULL
-};
-
-const struct decoder_plugin faad_decoder_plugin = {
- .name = "faad",
- .stream_decode = faad_stream_decode,
- .scan_stream = faad_scan_stream,
- .suffixes = faad_suffixes,
- .mime_types = faad_mime_types,
-};
diff --git a/src/decoder/ffmpeg_decoder_plugin.c b/src/decoder/ffmpeg_decoder_plugin.c
deleted file mode 100644
index 58bd2f54a..000000000
--- a/src/decoder/ffmpeg_decoder_plugin.c
+++ /dev/null
@@ -1,814 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "decoder_api.h"
-#include "audio_check.h"
-#include "ffmpeg_metadata.h"
-#include "tag_handler.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <stdio.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-
-#include <libavcodec/avcodec.h>
-#include <libavformat/avformat.h>
-#include <libavformat/avio.h>
-#include <libavutil/avutil.h>
-#include <libavutil/log.h>
-#include <libavutil/mathematics.h>
-#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,5,0)
-#include <libavutil/dict.h>
-#endif
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "ffmpeg"
-
-static GLogLevelFlags
-level_ffmpeg_to_glib(int level)
-{
- if (level <= AV_LOG_FATAL)
- return G_LOG_LEVEL_CRITICAL;
-
- if (level <= AV_LOG_ERROR)
- return G_LOG_LEVEL_WARNING;
-
- if (level <= AV_LOG_INFO)
- return G_LOG_LEVEL_MESSAGE;
-
- return G_LOG_LEVEL_DEBUG;
-}
-
-static void
-mpd_ffmpeg_log_callback(G_GNUC_UNUSED void *ptr, int level,
- const char *fmt, va_list vl)
-{
- const AVClass * cls = NULL;
-
- if (ptr != NULL)
- cls = *(const AVClass *const*)ptr;
-
- if (cls != NULL) {
- char *domain = g_strconcat(G_LOG_DOMAIN, "/", cls->item_name(ptr), NULL);
- g_logv(domain, level_ffmpeg_to_glib(level), fmt, vl);
- g_free(domain);
- }
-}
-
-struct mpd_ffmpeg_stream {
- struct decoder *decoder;
- struct input_stream *input;
-
-#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(52,101,0)
- AVIOContext *io;
-#else
- ByteIOContext *io;
-#endif
- unsigned char buffer[8192];
-};
-
-static int
-mpd_ffmpeg_stream_read(void *opaque, uint8_t *buf, int size)
-{
- struct mpd_ffmpeg_stream *stream = opaque;
-
- return decoder_read(stream->decoder, stream->input,
- (void *)buf, size);
-}
-
-static int64_t
-mpd_ffmpeg_stream_seek(void *opaque, int64_t pos, int whence)
-{
- struct mpd_ffmpeg_stream *stream = opaque;
-
- if (whence == AVSEEK_SIZE)
- return stream->input->size;
-
- if (!input_stream_lock_seek(stream->input, pos, whence, NULL))
- return -1;
-
- return stream->input->offset;
-}
-
-static struct mpd_ffmpeg_stream *
-mpd_ffmpeg_stream_open(struct decoder *decoder, struct input_stream *input)
-{
- struct mpd_ffmpeg_stream *stream = g_new(struct mpd_ffmpeg_stream, 1);
- stream->decoder = decoder;
- stream->input = input;
-#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(52,101,0)
- stream->io = avio_alloc_context(stream->buffer, sizeof(stream->buffer),
- false, stream,
- mpd_ffmpeg_stream_read, NULL,
- input->seekable
- ? mpd_ffmpeg_stream_seek : NULL);
-#else
- stream->io = av_alloc_put_byte(stream->buffer, sizeof(stream->buffer),
- false, stream,
- mpd_ffmpeg_stream_read, NULL,
- input->seekable
- ? mpd_ffmpeg_stream_seek : NULL);
-#endif
- if (stream->io == NULL) {
- g_free(stream);
- return NULL;
- }
-
- return stream;
-}
-
-/**
- * API compatibility wrapper for av_open_input_stream() and
- * avformat_open_input().
- */
-static int
-mpd_ffmpeg_open_input(AVFormatContext **ic_ptr,
-#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(52,101,0)
- AVIOContext *pb,
-#else
- ByteIOContext *pb,
-#endif
- const char *filename,
- AVInputFormat *fmt)
-{
-#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53,1,3)
- AVFormatContext *context = avformat_alloc_context();
- if (context == NULL)
- return AVERROR(ENOMEM);
-
- context->pb = pb;
- *ic_ptr = context;
- return avformat_open_input(ic_ptr, filename, fmt, NULL);
-#else
- return av_open_input_stream(ic_ptr, pb, filename, fmt, NULL);
-#endif
-}
-
-static void
-mpd_ffmpeg_stream_close(struct mpd_ffmpeg_stream *stream)
-{
- av_free(stream->io);
- g_free(stream);
-}
-
-static bool
-ffmpeg_init(G_GNUC_UNUSED const struct config_param *param)
-{
- av_log_set_callback(mpd_ffmpeg_log_callback);
-
- av_register_all();
- return true;
-}
-
-static int
-ffmpeg_find_audio_stream(const AVFormatContext *format_context)
-{
- for (unsigned i = 0; i < format_context->nb_streams; ++i)
- if (format_context->streams[i]->codec->codec_type ==
-#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52, 64, 0)
- AVMEDIA_TYPE_AUDIO)
-#else
- CODEC_TYPE_AUDIO)
-#endif
- return i;
-
- return -1;
-}
-
-#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(53,25,0)
-/**
- * On some platforms, libavcodec wants the output buffer aligned to 16
- * bytes (because it uses SSE/Altivec internally). This function
- * returns the aligned version of the specified buffer, and corrects
- * the buffer size.
- */
-static void *
-align16(void *p, size_t *length_p)
-{
- unsigned add = 16 - (size_t)p % 16;
-
- *length_p -= add;
- return (char *)p + add;
-}
-#endif
-
-G_GNUC_CONST
-static double
-time_from_ffmpeg(int64_t t, const AVRational time_base)
-{
- assert(t != (int64_t)AV_NOPTS_VALUE);
-
- return (double)av_rescale_q(t, time_base, (AVRational){1, 1024})
- / (double)1024;
-}
-
-G_GNUC_CONST
-static int64_t
-time_to_ffmpeg(double t, const AVRational time_base)
-{
- return av_rescale_q((int64_t)(t * 1024), (AVRational){1, 1024},
- time_base);
-}
-
-#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53,25,0)
-
-static void
-copy_interleave_frame2(uint8_t *dest, uint8_t **src,
- unsigned nframes, unsigned nchannels,
- unsigned sample_size)
-{
- for (unsigned frame = 0; frame < nframes; ++frame) {
- for (unsigned channel = 0; channel < nchannels; ++channel) {
- memcpy(dest, src[channel] + frame * sample_size,
- sample_size);
- dest += sample_size;
- }
- }
-}
-
-/**
- * Copy PCM data from a AVFrame to an interleaved buffer.
- */
-static int
-copy_interleave_frame(const AVCodecContext *codec_context,
- const AVFrame *frame,
- uint8_t *buffer, size_t buffer_size)
-{
- int plane_size;
- const int data_size =
- av_samples_get_buffer_size(&plane_size,
- codec_context->channels,
- frame->nb_samples,
- codec_context->sample_fmt, 1);
- if (buffer_size < (size_t)data_size)
- /* buffer is too small - shouldn't happen */
- return AVERROR(EINVAL);
-
- if (av_sample_fmt_is_planar(codec_context->sample_fmt) &&
- codec_context->channels > 1) {
- copy_interleave_frame2(buffer, frame->extended_data,
- frame->nb_samples,
- codec_context->channels,
- av_get_bytes_per_sample(codec_context->sample_fmt));
- } else {
- memcpy(buffer, frame->extended_data[0], data_size);
- }
-
- return data_size;
-}
-#endif
-
-static enum decoder_command
-ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is,
- const AVPacket *packet,
- AVCodecContext *codec_context,
- const AVRational *time_base)
-{
- if (packet->pts >= 0 && packet->pts != (int64_t)AV_NOPTS_VALUE)
- decoder_timestamp(decoder,
- time_from_ffmpeg(packet->pts, *time_base));
-
-#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52,25,0)
- AVPacket packet2 = *packet;
-#else
- const uint8_t *packet_data = packet->data;
- int packet_size = packet->size;
-#endif
-
-#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53,25,0)
- uint8_t aligned_buffer[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2 + 16];
- const size_t buffer_size = sizeof(aligned_buffer);
-#else
- /* libavcodec < 0.8 needs an aligned buffer */
- uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2 + 16];
- size_t buffer_size = sizeof(audio_buf);
- int16_t *aligned_buffer = align16(audio_buf, &buffer_size);
-#endif
-
- enum decoder_command cmd = DECODE_COMMAND_NONE;
- while (
-#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52,25,0)
- packet2.size > 0 &&
-#else
- packet_size > 0 &&
-#endif
- cmd == DECODE_COMMAND_NONE) {
- int audio_size = buffer_size;
-#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53,25,0)
-
- AVFrame *frame = avcodec_alloc_frame();
- if (frame == NULL) {
- g_warning("Could not allocate frame");
- break;
- }
-
- int got_frame = 0;
- int len = avcodec_decode_audio4(codec_context,
- frame, &got_frame,
- &packet2);
- if (len >= 0 && got_frame) {
- audio_size = copy_interleave_frame(codec_context,
- frame,
- aligned_buffer,
- buffer_size);
- if (audio_size < 0)
- len = audio_size;
- } else if (len >= 0)
- len = -1;
-
-#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 28, 0)
- avcodec_free_frame(&frame);
-#else
- av_freep(&frame);
-#endif
-
-#elif LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52,25,0)
- int len = avcodec_decode_audio3(codec_context,
- aligned_buffer, &audio_size,
- &packet2);
-#else
- int len = avcodec_decode_audio2(codec_context,
- aligned_buffer, &audio_size,
- packet_data, packet_size);
-#endif
-
- if (len < 0) {
- /* if error, we skip the frame */
- g_message("decoding failed, frame skipped\n");
- break;
- }
-
-#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52,25,0)
- packet2.data += len;
- packet2.size -= len;
-#else
- packet_data += len;
- packet_size -= len;
-#endif
-
- if (audio_size <= 0)
- continue;
-
- cmd = decoder_data(decoder, is,
- aligned_buffer, audio_size,
- codec_context->bit_rate / 1000);
- }
- return cmd;
-}
-
-#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(52, 94, 1)
-#define AVSampleFormat SampleFormat
-#endif
-
-G_GNUC_CONST
-static enum sample_format
-ffmpeg_sample_format(enum AVSampleFormat sample_fmt)
-{
- switch (sample_fmt) {
-#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52, 94, 1)
- case AV_SAMPLE_FMT_S16:
-#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,17,0)
- case AV_SAMPLE_FMT_S16P:
-#endif
-#else
- case SAMPLE_FMT_S16:
-#endif
- return SAMPLE_FORMAT_S16;
-
-#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52, 94, 1)
- case AV_SAMPLE_FMT_S32:
-#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,17,0)
- case AV_SAMPLE_FMT_S32P:
-#endif
-#else
- case SAMPLE_FMT_S32:
-#endif
- return SAMPLE_FORMAT_S32;
-
-#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,17,0)
- case AV_SAMPLE_FMT_FLTP:
- return SAMPLE_FORMAT_FLOAT;
-#endif
-
- default:
- break;
- }
-
-#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52, 94, 1)
- char buffer[64];
- const char *name = av_get_sample_fmt_string(buffer, sizeof(buffer),
- sample_fmt);
- if (name != NULL)
- g_warning("Unsupported libavcodec SampleFormat value: %s (%d)",
- name, sample_fmt);
- else
-#endif
- g_warning("Unsupported libavcodec SampleFormat value: %d",
- sample_fmt);
- return SAMPLE_FORMAT_UNDEFINED;
-}
-
-static AVInputFormat *
-ffmpeg_probe(struct decoder *decoder, struct input_stream *is)
-{
- enum {
- BUFFER_SIZE = 16384,
- PADDING = 16,
- };
-
- unsigned char *buffer = g_malloc(BUFFER_SIZE);
- size_t nbytes = decoder_read(decoder, is, buffer, BUFFER_SIZE);
- if (nbytes <= PADDING ||
- !input_stream_lock_seek(is, 0, SEEK_SET, NULL)) {
- g_free(buffer);
- return NULL;
- }
-
- /* some ffmpeg parsers (e.g. ac3_parser.c) read a few bytes
- beyond the declared buffer limit, which makes valgrind
- angry; this workaround removes some padding from the buffer
- size */
- nbytes -= PADDING;
-
- AVProbeData avpd = {
- .buf = buffer,
- .buf_size = nbytes,
- .filename = is->uri,
- };
-
- AVInputFormat *format = av_probe_input_format(&avpd, true);
- g_free(buffer);
-
- return format;
-}
-
-static void
-ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
-{
- AVInputFormat *input_format = ffmpeg_probe(decoder, input);
- if (input_format == NULL)
- return;
-
- g_debug("detected input format '%s' (%s)",
- input_format->name, input_format->long_name);
-
- struct mpd_ffmpeg_stream *stream =
- mpd_ffmpeg_stream_open(decoder, input);
- if (stream == NULL) {
- g_warning("Failed to open stream");
- return;
- }
-
- //ffmpeg works with ours "fileops" helper
- AVFormatContext *format_context = NULL;
- if (mpd_ffmpeg_open_input(&format_context, stream->io, input->uri,
- input_format) != 0) {
- g_warning("Open failed\n");
- mpd_ffmpeg_stream_close(stream);
- return;
- }
-
-#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,6,0)
- const int find_result =
- avformat_find_stream_info(format_context, NULL);
-#else
- const int find_result = av_find_stream_info(format_context);
-#endif
- if (find_result < 0) {
- g_warning("Couldn't find stream info\n");
-#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0)
- avformat_close_input(&format_context);
-#else
- av_close_input_stream(format_context);
-#endif
- mpd_ffmpeg_stream_close(stream);
- return;
- }
-
- int audio_stream = ffmpeg_find_audio_stream(format_context);
- if (audio_stream == -1) {
- g_warning("No audio stream inside\n");
-#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0)
- avformat_close_input(&format_context);
-#else
- av_close_input_stream(format_context);
-#endif
- mpd_ffmpeg_stream_close(stream);
- return;
- }
-
- AVStream *av_stream = format_context->streams[audio_stream];
-
- AVCodecContext *codec_context = av_stream->codec;
- if (codec_context->codec_name[0] != 0)
- g_debug("codec '%s'", codec_context->codec_name);
-
- AVCodec *codec = avcodec_find_decoder(codec_context->codec_id);
-
- if (!codec) {
- g_warning("Unsupported audio codec\n");
-#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0)
- avformat_close_input(&format_context);
-#else
- av_close_input_stream(format_context);
-#endif
- mpd_ffmpeg_stream_close(stream);
- return;
- }
-
- const enum sample_format sample_format =
- ffmpeg_sample_format(codec_context->sample_fmt);
- if (sample_format == SAMPLE_FORMAT_UNDEFINED)
- return;
-
- GError *error = NULL;
- struct audio_format audio_format;
- if (!audio_format_init_checked(&audio_format,
- codec_context->sample_rate,
- sample_format,
- codec_context->channels, &error)) {
- g_warning("%s", error->message);
- g_error_free(error);
-#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0)
- avformat_close_input(&format_context);
-#else
- av_close_input_stream(format_context);
-#endif
- mpd_ffmpeg_stream_close(stream);
- return;
- }
-
- /* the audio format must be read from AVCodecContext by now,
- because avcodec_open() has been demonstrated to fill bogus
- values into AVCodecContext.channels - a change that will be
- reverted later by avcodec_decode_audio3() */
-
-#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53,6,0)
- const int open_result = avcodec_open2(codec_context, codec, NULL);
-#else
- const int open_result = avcodec_open(codec_context, codec);
-#endif
- if (open_result < 0) {
- g_warning("Could not open codec\n");
-#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0)
- avformat_close_input(&format_context);
-#else
- av_close_input_stream(format_context);
-#endif
- mpd_ffmpeg_stream_close(stream);
- return;
- }
-
- int total_time = format_context->duration != (int64_t)AV_NOPTS_VALUE
- ? format_context->duration / AV_TIME_BASE
- : 0;
-
- decoder_initialized(decoder, &audio_format,
- input->seekable, total_time);
-
- enum decoder_command cmd;
- do {
- AVPacket packet;
- if (av_read_frame(format_context, &packet) < 0)
- /* end of file */
- break;
-
- if (packet.stream_index == audio_stream)
- cmd = ffmpeg_send_packet(decoder, input,
- &packet, codec_context,
- &av_stream->time_base);
- else
- cmd = decoder_get_command(decoder);
-
- av_free_packet(&packet);
-
- if (cmd == DECODE_COMMAND_SEEK) {
- int64_t where =
- time_to_ffmpeg(decoder_seek_where(decoder),
- av_stream->time_base);
-
- if (av_seek_frame(format_context, audio_stream, where,
- AV_TIME_BASE) < 0)
- decoder_seek_error(decoder);
- else {
- avcodec_flush_buffers(codec_context);
- decoder_command_finished(decoder);
- }
- }
- } while (cmd != DECODE_COMMAND_STOP);
-
- avcodec_close(codec_context);
-#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0)
- avformat_close_input(&format_context);
-#else
- av_close_input_stream(format_context);
-#endif
- mpd_ffmpeg_stream_close(stream);
-}
-
-//no tag reading in ffmpeg, check if playable
-static bool
-ffmpeg_scan_stream(struct input_stream *is,
- const struct tag_handler *handler, void *handler_ctx)
-{
- AVInputFormat *input_format = ffmpeg_probe(NULL, is);
- if (input_format == NULL)
- return false;
-
- struct mpd_ffmpeg_stream *stream = mpd_ffmpeg_stream_open(NULL, is);
- if (stream == NULL)
- return false;
-
- AVFormatContext *f = NULL;
- if (mpd_ffmpeg_open_input(&f, stream->io, is->uri,
- input_format) != 0) {
- mpd_ffmpeg_stream_close(stream);
- return false;
- }
-
-#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,6,0)
- const int find_result =
- avformat_find_stream_info(f, NULL);
-#else
- const int find_result = av_find_stream_info(f);
-#endif
- if (find_result < 0) {
-#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0)
- avformat_close_input(&f);
-#else
- av_close_input_stream(f);
-#endif
- mpd_ffmpeg_stream_close(stream);
- return false;
- }
-
- if (f->duration != (int64_t)AV_NOPTS_VALUE)
- tag_handler_invoke_duration(handler, handler_ctx,
- f->duration / AV_TIME_BASE);
-
-#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(52,101,0)
- av_metadata_conv(f, NULL, f->iformat->metadata_conv);
-#endif
-
- ffmpeg_scan_dictionary(f->metadata, handler, handler_ctx);
- int idx = ffmpeg_find_audio_stream(f);
- if (idx >= 0)
- ffmpeg_scan_dictionary(f->streams[idx]->metadata,
- handler, handler_ctx);
-
-#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0)
- avformat_close_input(&f);
-#else
- av_close_input_stream(f);
-#endif
- mpd_ffmpeg_stream_close(stream);
-
- return true;
-}
-
-/**
- * A list of extensions found for the formats supported by ffmpeg.
- * This list is current as of 02-23-09; To find out if there are more
- * supported formats, check the ffmpeg changelog since this date for
- * more formats.
- */
-static const char *const ffmpeg_suffixes[] = {
- "16sv", "3g2", "3gp", "4xm", "8svx", "aa3", "aac", "ac3", "afc", "aif",
- "aifc", "aiff", "al", "alaw", "amr", "anim", "apc", "ape", "asf",
- "atrac", "au", "aud", "avi", "avm2", "avs", "bap", "bfi", "c93", "cak",
- "cin", "cmv", "cpk", "daud", "dct", "divx", "dts", "dv", "dvd", "dxa",
- "eac3", "film", "flac", "flc", "fli", "fll", "flx", "flv", "g726",
- "gsm", "gxf", "iss", "m1v", "m2v", "m2t", "m2ts",
- "m4a", "m4b", "m4v",
- "mad",
- "mj2", "mjpeg", "mjpg", "mka", "mkv", "mlp", "mm", "mmf", "mov", "mp+",
- "mp1", "mp2", "mp3", "mp4", "mpc", "mpeg", "mpg", "mpga", "mpp", "mpu",
- "mve", "mvi", "mxf", "nc", "nsv", "nut", "nuv", "oga", "ogm", "ogv",
- "ogx", "oma", "ogg", "omg", "psp", "pva", "qcp", "qt", "r3d", "ra",
- "ram", "rl2", "rm", "rmvb", "roq", "rpl", "rvc", "shn", "smk", "snd",
- "sol", "son", "spx", "str", "swf", "tgi", "tgq", "tgv", "thp", "ts",
- "tsp", "tta", "xa", "xvid", "uv", "uv2", "vb", "vid", "vob", "voc",
- "vp6", "vmd", "wav", "webm", "wma", "wmv", "wsaud", "wsvga", "wv",
- "wve",
- NULL
-};
-
-static const char *const ffmpeg_mime_types[] = {
- "application/flv",
- "application/m4a",
- "application/mp4",
- "application/octet-stream",
- "application/ogg",
- "application/x-ms-wmz",
- "application/x-ms-wmd",
- "application/x-ogg",
- "application/x-shockwave-flash",
- "application/x-shorten",
- "audio/8svx",
- "audio/16sv",
- "audio/aac",
- "audio/ac3",
- "audio/aiff"
- "audio/amr",
- "audio/basic",
- "audio/flac",
- "audio/m4a",
- "audio/mp4",
- "audio/mpeg",
- "audio/musepack",
- "audio/ogg",
- "audio/qcelp",
- "audio/vorbis",
- "audio/vorbis+ogg",
- "audio/x-8svx",
- "audio/x-16sv",
- "audio/x-aac",
- "audio/x-ac3",
- "audio/x-aiff"
- "audio/x-alaw",
- "audio/x-au",
- "audio/x-dca",
- "audio/x-eac3",
- "audio/x-flac",
- "audio/x-gsm",
- "audio/x-mace",
- "audio/x-matroska",
- "audio/x-monkeys-audio",
- "audio/x-mpeg",
- "audio/x-ms-wma",
- "audio/x-ms-wax",
- "audio/x-musepack",
- "audio/x-ogg",
- "audio/x-vorbis",
- "audio/x-vorbis+ogg",
- "audio/x-pn-realaudio",
- "audio/x-pn-multirate-realaudio",
- "audio/x-speex",
- "audio/x-tta"
- "audio/x-voc",
- "audio/x-wav",
- "audio/x-wma",
- "audio/x-wv",
- "video/anim",
- "video/quicktime",
- "video/msvideo",
- "video/ogg",
- "video/theora",
- "video/webm",
- "video/x-dv",
- "video/x-flv",
- "video/x-matroska",
- "video/x-mjpeg",
- "video/x-mpeg",
- "video/x-ms-asf",
- "video/x-msvideo",
- "video/x-ms-wmv",
- "video/x-ms-wvx",
- "video/x-ms-wm",
- "video/x-ms-wmx",
- "video/x-nut",
- "video/x-pva",
- "video/x-theora",
- "video/x-vid",
- "video/x-wmv",
- "video/x-xvid",
-
- /* special value for the "ffmpeg" input plugin: all streams by
- the "ffmpeg" input plugin shall be decoded by this
- plugin */
- "audio/x-mpd-ffmpeg",
-
- NULL
-};
-
-const struct decoder_plugin ffmpeg_decoder_plugin = {
- .name = "ffmpeg",
- .init = ffmpeg_init,
- .stream_decode = ffmpeg_decode,
- .scan_stream = ffmpeg_scan_stream,
- .suffixes = ffmpeg_suffixes,
- .mime_types = ffmpeg_mime_types
-};
diff --git a/src/decoder/ffmpeg_metadata.c b/src/decoder/ffmpeg_metadata.c
deleted file mode 100644
index 3ef774f63..000000000
--- a/src/decoder/ffmpeg_metadata.c
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * 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 "ffmpeg_metadata.h"
-#include "tag_table.h"
-#include "tag_handler.h"
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "ffmpeg"
-
-static const struct tag_table ffmpeg_tags[] = {
-#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(52,50,0)
- { "author", TAG_ARTIST },
-#endif
- { "year", TAG_DATE },
- { "author-sort", TAG_ARTIST_SORT },
- { "album_artist", TAG_ALBUM_ARTIST },
- { "album_artist-sort", TAG_ALBUM_ARTIST_SORT },
-
- /* sentinel */
- { NULL, TAG_NUM_OF_ITEM_TYPES }
-};
-
-static void
-ffmpeg_copy_metadata(enum tag_type type,
- AVDictionary *m, const char *name,
- const struct tag_handler *handler, void *handler_ctx)
-{
- AVDictionaryEntry *mt = NULL;
-
- while ((mt = av_dict_get(m, name, mt, 0)) != NULL)
- tag_handler_invoke_tag(handler, handler_ctx,
- type, mt->value);
-}
-
-#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,5,0)
-
-static void
-ffmpeg_scan_pairs(AVDictionary *dict,
- const struct tag_handler *handler, void *handler_ctx)
-{
- AVDictionaryEntry *i = NULL;
-
- while ((i = av_dict_get(dict, "", i, AV_DICT_IGNORE_SUFFIX)) != NULL)
- tag_handler_invoke_pair(handler, handler_ctx,
- i->key, i->value);
-}
-
-#endif
-
-void
-ffmpeg_scan_dictionary(AVDictionary *dict,
- const struct tag_handler *handler, void *handler_ctx)
-{
- for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
- ffmpeg_copy_metadata(i, dict, tag_item_names[i],
- handler, handler_ctx);
-
- for (const struct tag_table *i = ffmpeg_tags;
- i->name != NULL; ++i)
- ffmpeg_copy_metadata(i->type, dict, i->name,
- handler, handler_ctx);
-
-#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,5,0)
- if (handler->pair != NULL)
- ffmpeg_scan_pairs(dict, handler, handler_ctx);
-#endif
-}
diff --git a/src/decoder/ffmpeg_metadata.h b/src/decoder/ffmpeg_metadata.h
deleted file mode 100644
index 60658f479..000000000
--- a/src/decoder/ffmpeg_metadata.h
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef MPD_FFMPEG_METADATA_H
-#define MPD_FFMPEG_METADATA_H
-
-#include <libavformat/avformat.h>
-#include <libavutil/avutil.h>
-#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,5,0)
-#include <libavutil/dict.h>
-#endif
-
-#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(53,1,0)
-#define AVDictionary AVMetadata
-#define AVDictionaryEntry AVMetadataTag
-#define av_dict_get av_metadata_get
-#endif
-
-struct tag_handler;
-
-void
-ffmpeg_scan_dictionary(AVDictionary *dict,
- const struct tag_handler *handler, void *handler_ctx);
-
-#endif
diff --git a/src/decoder/flac_compat.h b/src/decoder/flac_compat.h
deleted file mode 100644
index 9a30acc26..000000000
--- a/src/decoder/flac_compat.h
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * Common data structures and functions used by FLAC and OggFLAC
- */
-
-#ifndef MPD_FLAC_COMPAT_H
-#define MPD_FLAC_COMPAT_H
-
-#include <FLAC/export.h>
-#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7
-# include <FLAC/seekable_stream_decoder.h>
-
-/* starting with libFLAC 1.1.3, the SeekableStreamDecoder has been
- merged into the StreamDecoder. The following macros try to emulate
- the new API for libFLAC 1.1.2 by mapping MPD's StreamDecoder calls
- to the old SeekableStreamDecoder API. */
-
-#define FLAC__StreamDecoder FLAC__SeekableStreamDecoder
-#define FLAC__stream_decoder_new FLAC__seekable_stream_decoder_new
-#define FLAC__stream_decoder_get_decode_position FLAC__seekable_stream_decoder_get_decode_position
-#define FLAC__stream_decoder_get_state FLAC__seekable_stream_decoder_get_state
-#define FLAC__stream_decoder_process_single FLAC__seekable_stream_decoder_process_single
-#define FLAC__stream_decoder_process_until_end_of_metadata FLAC__seekable_stream_decoder_process_until_end_of_metadata
-#define FLAC__stream_decoder_seek_absolute FLAC__seekable_stream_decoder_seek_absolute
-#define FLAC__stream_decoder_finish FLAC__seekable_stream_decoder_finish
-#define FLAC__stream_decoder_delete FLAC__seekable_stream_decoder_delete
-
-#define FLAC__STREAM_DECODER_END_OF_STREAM FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM
-
-typedef unsigned flac_read_status_size_t;
-
-#define FLAC__StreamDecoderReadStatus FLAC__SeekableStreamDecoderReadStatus
-#define FLAC__STREAM_DECODER_READ_STATUS_CONTINUE FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK
-#define FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK
-#define FLAC__STREAM_DECODER_READ_STATUS_ABORT FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR
-
-#define FLAC__StreamDecoderSeekStatus FLAC__SeekableStreamDecoderSeekStatus
-#define FLAC__STREAM_DECODER_SEEK_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK
-#define FLAC__STREAM_DECODER_SEEK_STATUS_ERROR FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR
-#define FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR
-
-#define FLAC__StreamDecoderTellStatus FLAC__SeekableStreamDecoderTellStatus
-#define FLAC__STREAM_DECODER_TELL_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_OK
-#define FLAC__STREAM_DECODER_TELL_STATUS_ERROR FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR
-#define FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR
-
-#define FLAC__StreamDecoderLengthStatus FLAC__SeekableStreamDecoderLengthStatus
-#define FLAC__STREAM_DECODER_LENGTH_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_OK
-#define FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR
-#define FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR
-
-typedef enum {
- FLAC__STREAM_DECODER_INIT_STATUS_OK,
- FLAC__STREAM_DECODER_INIT_STATUS_ERROR,
-} FLAC__StreamDecoderInitStatus;
-
-static inline FLAC__StreamDecoderInitStatus
-FLAC__stream_decoder_init_stream(FLAC__SeekableStreamDecoder *decoder,
- FLAC__SeekableStreamDecoderReadCallback read_cb,
- FLAC__SeekableStreamDecoderSeekCallback seek_cb,
- FLAC__SeekableStreamDecoderTellCallback tell_cb,
- FLAC__SeekableStreamDecoderLengthCallback length_cb,
- FLAC__SeekableStreamDecoderEofCallback eof_cb,
- FLAC__SeekableStreamDecoderWriteCallback write_cb,
- FLAC__SeekableStreamDecoderMetadataCallback metadata_cb,
- FLAC__SeekableStreamDecoderErrorCallback error_cb,
- void *data)
-{
- return FLAC__seekable_stream_decoder_set_read_callback(decoder, read_cb) &&
- FLAC__seekable_stream_decoder_set_seek_callback(decoder, seek_cb) &&
- FLAC__seekable_stream_decoder_set_tell_callback(decoder, tell_cb) &&
- FLAC__seekable_stream_decoder_set_length_callback(decoder, length_cb) &&
- FLAC__seekable_stream_decoder_set_eof_callback(decoder, eof_cb) &&
- FLAC__seekable_stream_decoder_set_write_callback(decoder, write_cb) &&
- FLAC__seekable_stream_decoder_set_metadata_callback(decoder, metadata_cb) &&
- FLAC__seekable_stream_decoder_set_metadata_respond(decoder, FLAC__METADATA_TYPE_VORBIS_COMMENT) &&
- FLAC__seekable_stream_decoder_set_error_callback(decoder, error_cb) &&
- FLAC__seekable_stream_decoder_set_client_data(decoder, data) &&
- FLAC__seekable_stream_decoder_init(decoder) == FLAC__SEEKABLE_STREAM_DECODER_OK
- ? FLAC__STREAM_DECODER_INIT_STATUS_OK
- : FLAC__STREAM_DECODER_INIT_STATUS_ERROR;
-}
-
-#else /* FLAC_API_VERSION_CURRENT > 7 */
-
-# include <FLAC/stream_decoder.h>
-
-# define flac_init(a,b,c,d,e,f,g,h,i,j) \
- (FLAC__stream_decoder_init_stream(a,b,c,d,e,f,g,h,i,j) \
- == FLAC__STREAM_DECODER_INIT_STATUS_OK)
-
-typedef size_t flac_read_status_size_t;
-
-#endif /* FLAC_API_VERSION_CURRENT >= 7 */
-
-#endif /* _FLAC_COMMON_H */
diff --git a/src/decoder/flac_decoder_plugin.c b/src/decoder/flac_decoder_plugin.c
deleted file mode 100644
index fb0b3502d..000000000
--- a/src/decoder/flac_decoder_plugin.c
+++ /dev/null
@@ -1,486 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h" /* must be first for large file support */
-#include "_flac_common.h"
-#include "flac_compat.h"
-#include "flac_metadata.h"
-
-#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
-#include "_ogg_common.h"
-#endif
-
-#include <glib.h>
-
-#include <assert.h>
-#include <unistd.h>
-
-#include <sys/stat.h>
-#include <sys/types.h>
-
-/* this code was based on flac123, from flac-tools */
-
-static FLAC__StreamDecoderReadStatus
-flac_read_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd,
- FLAC__byte buf[], flac_read_status_size_t *bytes,
- void *fdata)
-{
- struct flac_data *data = fdata;
- size_t r;
-
- r = decoder_read(data->decoder, data->input_stream,
- (void *)buf, *bytes);
- *bytes = r;
-
- if (r == 0) {
- if (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE ||
- input_stream_lock_eof(data->input_stream))
- return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM;
- else
- return FLAC__STREAM_DECODER_READ_STATUS_ABORT;
- }
-
- return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
-}
-
-static FLAC__StreamDecoderSeekStatus
-flac_seek_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd,
- FLAC__uint64 offset, void *fdata)
-{
- struct flac_data *data = (struct flac_data *) fdata;
-
- if (!data->input_stream->seekable)
- return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED;
-
- if (!input_stream_lock_seek(data->input_stream, offset, SEEK_SET,
- NULL))
- return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR;
-
- return FLAC__STREAM_DECODER_SEEK_STATUS_OK;
-}
-
-static FLAC__StreamDecoderTellStatus
-flac_tell_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd,
- FLAC__uint64 * offset, void *fdata)
-{
- struct flac_data *data = (struct flac_data *) fdata;
-
- if (!data->input_stream->seekable)
- return FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED;
-
- *offset = (long)(data->input_stream->offset);
-
- return FLAC__STREAM_DECODER_TELL_STATUS_OK;
-}
-
-static FLAC__StreamDecoderLengthStatus
-flac_length_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd,
- FLAC__uint64 * length, void *fdata)
-{
- struct flac_data *data = (struct flac_data *) fdata;
-
- if (data->input_stream->size < 0)
- return FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED;
-
- *length = (size_t) (data->input_stream->size);
-
- return FLAC__STREAM_DECODER_LENGTH_STATUS_OK;
-}
-
-static FLAC__bool
-flac_eof_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, void *fdata)
-{
- struct flac_data *data = (struct flac_data *) fdata;
-
- return (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE &&
- decoder_get_command(data->decoder) != DECODE_COMMAND_SEEK) ||
- input_stream_lock_eof(data->input_stream);
-}
-
-static void
-flac_error_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd,
- FLAC__StreamDecoderErrorStatus status, void *fdata)
-{
- flac_error_common_cb(status, (struct flac_data *) fdata);
-}
-
-#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7
-static void flacPrintErroredState(FLAC__SeekableStreamDecoderState state)
-{
- switch (state) {
- case FLAC__SEEKABLE_STREAM_DECODER_OK:
- case FLAC__SEEKABLE_STREAM_DECODER_SEEKING:
- case FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM:
- return;
-
- case FLAC__SEEKABLE_STREAM_DECODER_MEMORY_ALLOCATION_ERROR:
- case FLAC__SEEKABLE_STREAM_DECODER_READ_ERROR:
- case FLAC__SEEKABLE_STREAM_DECODER_SEEK_ERROR:
- case FLAC__SEEKABLE_STREAM_DECODER_STREAM_DECODER_ERROR:
- case FLAC__SEEKABLE_STREAM_DECODER_ALREADY_INITIALIZED:
- case FLAC__SEEKABLE_STREAM_DECODER_INVALID_CALLBACK:
- case FLAC__SEEKABLE_STREAM_DECODER_UNINITIALIZED:
- break;
- }
-
- g_warning("%s\n", FLAC__SeekableStreamDecoderStateString[state]);
-}
-#else /* FLAC_API_VERSION_CURRENT >= 7 */
-static void flacPrintErroredState(FLAC__StreamDecoderState state)
-{
- switch (state) {
- case FLAC__STREAM_DECODER_SEARCH_FOR_METADATA:
- case FLAC__STREAM_DECODER_READ_METADATA:
- case FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC:
- case FLAC__STREAM_DECODER_READ_FRAME:
- case FLAC__STREAM_DECODER_END_OF_STREAM:
- return;
-
- case FLAC__STREAM_DECODER_OGG_ERROR:
- case FLAC__STREAM_DECODER_SEEK_ERROR:
- case FLAC__STREAM_DECODER_ABORTED:
- case FLAC__STREAM_DECODER_MEMORY_ALLOCATION_ERROR:
- case FLAC__STREAM_DECODER_UNINITIALIZED:
- break;
- }
-
- g_warning("%s\n", FLAC__StreamDecoderStateString[state]);
-}
-#endif /* FLAC_API_VERSION_CURRENT >= 7 */
-
-static void flacMetadata(G_GNUC_UNUSED const FLAC__StreamDecoder * dec,
- const FLAC__StreamMetadata * block, void *vdata)
-{
- flac_metadata_common_cb(block, (struct flac_data *) vdata);
-}
-
-static FLAC__StreamDecoderWriteStatus
-flac_write_cb(const FLAC__StreamDecoder *dec, const FLAC__Frame *frame,
- const FLAC__int32 *const buf[], void *vdata)
-{
- struct flac_data *data = (struct flac_data *) vdata;
- FLAC__uint64 nbytes = 0;
-
- if (FLAC__stream_decoder_get_decode_position(dec, &nbytes)) {
- if (data->position > 0 && nbytes > data->position) {
- nbytes -= data->position;
- data->position += nbytes;
- } else {
- data->position = nbytes;
- nbytes = 0;
- }
- } else
- nbytes = 0;
-
- return flac_common_write(data, frame, buf, nbytes);
-}
-
-static bool
-flac_scan_file(const char *file,
- const struct tag_handler *handler, void *handler_ctx)
-{
- return flac_scan_file2(file, NULL, handler, handler_ctx);
-}
-
-/**
- * Some glue code around FLAC__stream_decoder_new().
- */
-static FLAC__StreamDecoder *
-flac_decoder_new(void)
-{
- FLAC__StreamDecoder *sd = FLAC__stream_decoder_new();
- if (sd == NULL) {
- g_warning("FLAC__stream_decoder_new() failed");
- return NULL;
- }
-
-#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
- if(!FLAC__stream_decoder_set_metadata_respond(sd, FLAC__METADATA_TYPE_VORBIS_COMMENT))
- g_debug("FLAC__stream_decoder_set_metadata_respond() has failed");
-#endif
-
- return sd;
-}
-
-static bool
-flac_decoder_initialize(struct flac_data *data, FLAC__StreamDecoder *sd,
- FLAC__uint64 duration)
-{
- data->total_frames = duration;
-
- if (!FLAC__stream_decoder_process_until_end_of_metadata(sd)) {
- g_warning("problem reading metadata");
- return false;
- }
-
- if (data->initialized) {
- /* done */
- decoder_initialized(data->decoder, &data->audio_format,
- data->input_stream->seekable,
- (float)data->total_frames /
- (float)data->audio_format.sample_rate);
- return true;
- }
-
- if (data->input_stream->seekable)
- /* allow the workaround below only for nonseekable
- streams*/
- return false;
-
- /* no stream_info packet found; try to initialize the decoder
- from the first frame header */
- FLAC__stream_decoder_process_single(sd);
- return data->initialized;
-}
-
-static void
-flac_decoder_loop(struct flac_data *data, FLAC__StreamDecoder *flac_dec,
- FLAC__uint64 t_start, FLAC__uint64 t_end)
-{
- struct decoder *decoder = data->decoder;
- enum decoder_command cmd;
-
- data->first_frame = t_start;
-
- while (true) {
- if (data->tag != NULL && !tag_is_empty(data->tag)) {
- cmd = decoder_tag(data->decoder, data->input_stream,
- data->tag);
- tag_free(data->tag);
- data->tag = tag_new();
- } else
- cmd = decoder_get_command(decoder);
-
- if (cmd == DECODE_COMMAND_SEEK) {
- FLAC__uint64 seek_sample = t_start +
- decoder_seek_where(decoder) *
- data->audio_format.sample_rate;
- if (seek_sample >= t_start &&
- (t_end == 0 || seek_sample <= t_end) &&
- FLAC__stream_decoder_seek_absolute(flac_dec, seek_sample)) {
- data->next_frame = seek_sample;
- data->position = 0;
- decoder_command_finished(decoder);
- } else
- decoder_seek_error(decoder);
- } else if (cmd == DECODE_COMMAND_STOP ||
- FLAC__stream_decoder_get_state(flac_dec) == FLAC__STREAM_DECODER_END_OF_STREAM)
- break;
-
- if (t_end != 0 && data->next_frame >= t_end)
- /* end of this sub track */
- break;
-
- if (!FLAC__stream_decoder_process_single(flac_dec) &&
- decoder_get_command(decoder) == DECODE_COMMAND_NONE) {
- /* a failure that was not triggered by a
- decoder command */
- flacPrintErroredState(FLAC__stream_decoder_get_state(flac_dec));
- break;
- }
- }
-}
-
-static FLAC__StreamDecoderInitStatus
-stream_init_oggflac(FLAC__StreamDecoder *flac_dec, struct flac_data *data)
-{
-#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
- return FLAC__stream_decoder_init_ogg_stream(flac_dec,
- flac_read_cb,
- flac_seek_cb,
- flac_tell_cb,
- flac_length_cb,
- flac_eof_cb,
- flac_write_cb,
- flacMetadata,
- flac_error_cb,
- data);
-#else
- (void)flac_dec;
- (void)data;
-
- return FLAC__STREAM_DECODER_INIT_STATUS_ERROR;
-#endif
-}
-
-static FLAC__StreamDecoderInitStatus
-stream_init_flac(FLAC__StreamDecoder *flac_dec, struct flac_data *data)
-{
- return FLAC__stream_decoder_init_stream(flac_dec,
- flac_read_cb, flac_seek_cb,
- flac_tell_cb, flac_length_cb,
- flac_eof_cb, flac_write_cb,
- flacMetadata,
- flac_error_cb,
- data);
-}
-
-static FLAC__StreamDecoderInitStatus
-stream_init(FLAC__StreamDecoder *flac_dec, struct flac_data *data, bool is_ogg)
-{
- return is_ogg
- ? stream_init_oggflac(flac_dec, data)
- : stream_init_flac(flac_dec, data);
-}
-
-static void
-flac_decode_internal(struct decoder * decoder,
- struct input_stream *input_stream,
- bool is_ogg)
-{
- FLAC__StreamDecoder *flac_dec;
- struct flac_data data;
-
- flac_dec = flac_decoder_new();
- if (flac_dec == NULL)
- return;
-
- flac_data_init(&data, decoder, input_stream);
- data.tag = tag_new();
-
- FLAC__StreamDecoderInitStatus status =
- stream_init(flac_dec, &data, is_ogg);
- if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) {
- flac_data_deinit(&data);
- FLAC__stream_decoder_delete(flac_dec);
-#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
- g_warning("%s", FLAC__StreamDecoderInitStatusString[status]);
-#endif
- return;
- }
-
- if (!flac_decoder_initialize(&data, flac_dec, 0)) {
- flac_data_deinit(&data);
- FLAC__stream_decoder_finish(flac_dec);
- FLAC__stream_decoder_delete(flac_dec);
- return;
- }
-
- flac_decoder_loop(&data, flac_dec, 0, 0);
-
- flac_data_deinit(&data);
-
- FLAC__stream_decoder_finish(flac_dec);
- FLAC__stream_decoder_delete(flac_dec);
-}
-
-static void
-flac_decode(struct decoder * decoder, struct input_stream *input_stream)
-{
- flac_decode_internal(decoder, input_stream, false);
-}
-
-#ifndef HAVE_OGGFLAC
-
-static bool
-oggflac_init(G_GNUC_UNUSED const struct config_param *param)
-{
-#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
- return !!FLAC_API_SUPPORTS_OGG_FLAC;
-#else
- /* disable oggflac when libflac is too old */
- return false;
-#endif
-}
-
-#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
-
-static bool
-oggflac_scan_file(const char *file,
- const struct tag_handler *handler, void *handler_ctx)
-{
- FLAC__Metadata_Iterator *it;
- FLAC__StreamMetadata *block;
- FLAC__Metadata_Chain *chain = FLAC__metadata_chain_new();
-
- if (!(FLAC__metadata_chain_read_ogg(chain, file))) {
- FLAC__metadata_chain_delete(chain);
- return false;
- }
-
- it = FLAC__metadata_iterator_new();
- FLAC__metadata_iterator_init(it, chain);
-
- do {
- if (!(block = FLAC__metadata_iterator_get_block(it)))
- break;
-
- flac_scan_metadata(NULL, block,
- handler, handler_ctx);
- } while (FLAC__metadata_iterator_next(it));
- FLAC__metadata_iterator_delete(it);
-
- FLAC__metadata_chain_delete(chain);
- return true;
-}
-
-static void
-oggflac_decode(struct decoder *decoder, struct input_stream *input_stream)
-{
- if (ogg_stream_type_detect(input_stream) != FLAC)
- return;
-
- /* rewind the stream, because ogg_stream_type_detect() has
- moved it */
- input_stream_lock_seek(input_stream, 0, SEEK_SET, NULL);
-
- flac_decode_internal(decoder, input_stream, true);
-}
-
-static const char *const oggflac_suffixes[] = { "ogg", "oga", NULL };
-static const char *const oggflac_mime_types[] = {
- "application/ogg",
- "application/x-ogg",
- "audio/ogg",
- "audio/x-flac+ogg",
- "audio/x-ogg",
- NULL
-};
-
-#endif /* FLAC_API_VERSION_CURRENT >= 7 */
-
-const struct decoder_plugin oggflac_decoder_plugin = {
- .name = "oggflac",
- .init = oggflac_init,
-#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
- .stream_decode = oggflac_decode,
- .scan_file = oggflac_scan_file,
- .suffixes = oggflac_suffixes,
- .mime_types = oggflac_mime_types
-#endif
-};
-
-#endif /* HAVE_OGGFLAC */
-
-static const char *const flac_suffixes[] = { "flac", NULL };
-static const char *const flac_mime_types[] = {
- "application/flac",
- "application/x-flac",
- "audio/flac",
- "audio/x-flac",
- NULL
-};
-
-const struct decoder_plugin flac_decoder_plugin = {
- .name = "flac",
- .stream_decode = flac_decode,
- .scan_file = flac_scan_file,
- .suffixes = flac_suffixes,
- .mime_types = flac_mime_types,
-};
diff --git a/src/decoder/flac_metadata.c b/src/decoder/flac_metadata.c
deleted file mode 100644
index bd1eaf323..000000000
--- a/src/decoder/flac_metadata.c
+++ /dev/null
@@ -1,323 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "flac_metadata.h"
-#include "replay_gain_info.h"
-#include "tag.h"
-#include "tag_handler.h"
-#include "tag_table.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <stdbool.h>
-#include <stdlib.h>
-
-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)
- 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;
-
- return true;
-}
-
-bool
-flac_parse_replay_gain(struct replay_gain_info *rgi,
- const FLAC__StreamMetadata *block)
-{
- bool found = false;
-
- replay_gain_info_init(rgi);
-
- if (flac_find_float_comment(block, "replaygain_album_gain",
- &rgi->tuples[REPLAY_GAIN_ALBUM].gain))
- found = true;
- if (flac_find_float_comment(block, "replaygain_album_peak",
- &rgi->tuples[REPLAY_GAIN_ALBUM].peak))
- found = true;
- if (flac_find_float_comment(block, "replaygain_track_gain",
- &rgi->tuples[REPLAY_GAIN_TRACK].gain))
- found = true;
- if (flac_find_float_comment(block, "replaygain_track_peak",
- &rgi->tuples[REPLAY_GAIN_TRACK].peak))
- found = true;
-
- return found;
-}
-
-static bool
-flac_find_string_comment(const FLAC__StreamMetadata *block,
- const char *cmnt, char **str)
-{
- int offset;
- size_t pos;
- int len;
- const unsigned char *p;
-
- *str = NULL;
- 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)
- return false;
-
- p = &block->data.vorbis_comment.comments[offset].entry[pos];
- *str = g_strndup((const char *)p, len);
-
- return true;
-}
-
-bool
-flac_parse_mixramp(char **mixramp_start, char **mixramp_end,
- const FLAC__StreamMetadata *block)
-{
- bool found = false;
-
- if (flac_find_string_comment(block, "mixramp_start", mixramp_start))
- found = true;
- if (flac_find_string_comment(block, "mixramp_end", mixramp_end))
- found = true;
-
- return found;
-}
-
-/**
- * Checks if the specified name matches the entry's name, and if yes,
- * returns the comment value (not null-temrinated).
- */
-static const char *
-flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry,
- const char *name, const char *char_tnum, size_t *length_r)
-{
- size_t name_length = strlen(name);
- size_t char_tnum_length = 0;
- const char *comment = (const char*)entry->entry;
-
- if (entry->length <= name_length ||
- g_ascii_strncasecmp(comment, name, name_length) != 0)
- return NULL;
-
- if (char_tnum != NULL) {
- char_tnum_length = strlen(char_tnum);
- if (entry->length > name_length + char_tnum_length + 2 &&
- comment[name_length] == '[' &&
- g_ascii_strncasecmp(comment + name_length + 1,
- char_tnum, char_tnum_length) == 0 &&
- comment[name_length + char_tnum_length + 1] == ']')
- name_length = name_length + char_tnum_length + 2;
- else if (entry->length > name_length + char_tnum_length &&
- g_ascii_strncasecmp(comment + name_length,
- char_tnum, char_tnum_length) == 0)
- name_length = name_length + char_tnum_length;
- }
-
- if (comment[name_length] == '=') {
- *length_r = entry->length - name_length - 1;
- return comment + name_length + 1;
- }
-
- return NULL;
-}
-
-/**
- * Check if the comment's name equals the passed name, and if so, copy
- * the comment value into the tag.
- */
-static bool
-flac_copy_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry,
- const char *name, enum tag_type tag_type,
- const char *char_tnum,
- const struct tag_handler *handler, void *handler_ctx)
-{
- const char *value;
- size_t value_length;
-
- value = flac_comment_value(entry, name, char_tnum, &value_length);
- if (value != NULL) {
- char *p = g_strndup(value, value_length);
- tag_handler_invoke_tag(handler, handler_ctx, tag_type, p);
- g_free(p);
- return true;
- }
-
- return false;
-}
-
-static const struct tag_table flac_tags[] = {
- { "tracknumber", TAG_TRACK },
- { "discnumber", TAG_DISC },
- { "album artist", TAG_ALBUM_ARTIST },
- { NULL, TAG_NUM_OF_ITEM_TYPES }
-};
-
-static void
-flac_scan_comment(const char *char_tnum,
- const FLAC__StreamMetadata_VorbisComment_Entry *entry,
- const struct tag_handler *handler, void *handler_ctx)
-{
- if (handler->pair != NULL) {
- char *name = g_strdup((const char*)entry->entry);
- char *value = strchr(name, '=');
-
- if (value != NULL && value > name) {
- *value++ = 0;
- tag_handler_invoke_pair(handler, handler_ctx,
- name, value);
- }
-
- g_free(name);
- }
-
- for (const struct tag_table *i = flac_tags; i->name != NULL; ++i)
- if (flac_copy_comment(entry, i->name, i->type, char_tnum,
- handler, handler_ctx))
- return;
-
- for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
- if (flac_copy_comment(entry,
- tag_item_names[i], i, char_tnum,
- handler, handler_ctx))
- return;
-}
-
-static void
-flac_scan_comments(const char *char_tnum,
- const FLAC__StreamMetadata_VorbisComment *comment,
- const struct tag_handler *handler, void *handler_ctx)
-{
- for (unsigned i = 0; i < comment->num_comments; ++i)
- flac_scan_comment(char_tnum, &comment->comments[i],
- handler, handler_ctx);
-}
-
-void
-flac_scan_metadata(const char *track,
- const FLAC__StreamMetadata *block,
- const struct tag_handler *handler, void *handler_ctx)
-{
- switch (block->type) {
- case FLAC__METADATA_TYPE_VORBIS_COMMENT:
- flac_scan_comments(track, &block->data.vorbis_comment,
- handler, handler_ctx);
- break;
-
- case FLAC__METADATA_TYPE_STREAMINFO:
- if (block->data.stream_info.sample_rate > 0)
- tag_handler_invoke_duration(handler, handler_ctx,
- flac_duration(&block->data.stream_info));
- break;
-
- default:
- break;
- }
-}
-
-void
-flac_vorbis_comments_to_tag(struct tag *tag, const char *char_tnum,
- const FLAC__StreamMetadata_VorbisComment *comment)
-{
- flac_scan_comments(char_tnum, comment,
- &add_tag_handler, tag);
-}
-
-bool
-flac_scan_file2(const char *file, const char *char_tnum,
- const struct tag_handler *handler, void *handler_ctx)
-{
- FLAC__Metadata_SimpleIterator *it;
- FLAC__StreamMetadata *block = NULL;
-
- it = FLAC__metadata_simple_iterator_new();
- if (!FLAC__metadata_simple_iterator_init(it, file, 1, 0)) {
- const char *err;
- FLAC_API FLAC__Metadata_SimpleIteratorStatus s;
-
- s = FLAC__metadata_simple_iterator_status(it);
-
- switch (s) { /* slightly more human-friendly messages: */
- case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ILLEGAL_INPUT:
- err = "illegal input";
- break;
- case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ERROR_OPENING_FILE:
- err = "error opening file";
- break;
- case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_NOT_A_FLAC_FILE:
- err = "not a FLAC file";
- break;
- default:
- err = FLAC__Metadata_SimpleIteratorStatusString[s];
- }
- g_debug("Reading '%s' metadata gave the following error: %s\n",
- file, err);
- FLAC__metadata_simple_iterator_delete(it);
- return false;
- }
-
- do {
- block = FLAC__metadata_simple_iterator_get_block(it);
- if (!block)
- break;
-
- flac_scan_metadata(char_tnum, block, handler, handler_ctx);
- FLAC__metadata_object_delete(block);
- } while (FLAC__metadata_simple_iterator_next(it));
-
- FLAC__metadata_simple_iterator_delete(it);
-
- return true;
-}
-
-struct tag *
-flac_tag_load(const char *file, const char *char_tnum)
-{
- struct tag *tag = tag_new();
-
- if (!flac_scan_file2(file, char_tnum, &add_tag_handler, tag) ||
- tag_is_empty(tag)) {
- tag_free(tag);
- tag = NULL;
- }
-
- return tag;
-}
diff --git a/src/decoder/flac_metadata.h b/src/decoder/flac_metadata.h
deleted file mode 100644
index 3c463d5d6..000000000
--- a/src/decoder/flac_metadata.h
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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_FLAC_METADATA_H
-#define MPD_FLAC_METADATA_H
-
-#include <assert.h>
-#include <stdbool.h>
-#include <FLAC/metadata.h>
-
-struct tag_handler;
-struct tag;
-struct replay_gain_info;
-
-static inline unsigned
-flac_duration(const FLAC__StreamMetadata_StreamInfo *stream_info)
-{
- assert(stream_info->sample_rate > 0);
-
- return (stream_info->total_samples + stream_info->sample_rate - 1) /
- stream_info->sample_rate;
-}
-
-bool
-flac_parse_replay_gain(struct replay_gain_info *rgi,
- const FLAC__StreamMetadata *block);
-
-bool
-flac_parse_mixramp(char **mixramp_start, char **mixramp_end,
- const FLAC__StreamMetadata *block);
-
-void
-flac_vorbis_comments_to_tag(struct tag *tag, const char *char_tnum,
- const FLAC__StreamMetadata_VorbisComment *comment);
-
-void
-flac_scan_metadata(const char *track,
- const FLAC__StreamMetadata *block,
- const struct tag_handler *handler, void *handler_ctx);
-
-bool
-flac_scan_file2(const char *file, const char *char_tnum,
- const struct tag_handler *handler, void *handler_ctx);
-
-struct tag *
-flac_tag_load(const char *file, const char *char_tnum);
-
-#endif
diff --git a/src/decoder/flac_pcm.c b/src/decoder/flac_pcm.c
deleted file mode 100644
index 6964d8ac6..000000000
--- a/src/decoder/flac_pcm.c
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "flac_pcm.h"
-
-#include <assert.h>
-
-static void flac_convert_stereo16(int16_t *dest,
- const FLAC__int32 * const buf[],
- unsigned int position, unsigned int end)
-{
- for (; position < end; ++position) {
- *dest++ = buf[0][position];
- *dest++ = buf[1][position];
- }
-}
-
-static void
-flac_convert_16(int16_t *dest,
- unsigned int num_channels,
- const FLAC__int32 * const buf[],
- unsigned int position, unsigned int end)
-{
- unsigned int c_chan;
-
- for (; position < end; ++position)
- for (c_chan = 0; c_chan < num_channels; c_chan++)
- *dest++ = buf[c_chan][position];
-}
-
-/**
- * Note: this function also handles 24 bit files!
- */
-static void
-flac_convert_32(int32_t *dest,
- unsigned int num_channels,
- const FLAC__int32 * const buf[],
- unsigned int position, unsigned int end)
-{
- unsigned int c_chan;
-
- for (; position < end; ++position)
- for (c_chan = 0; c_chan < num_channels; c_chan++)
- *dest++ = buf[c_chan][position];
-}
-
-static void
-flac_convert_8(int8_t *dest,
- unsigned int num_channels,
- const FLAC__int32 * const buf[],
- unsigned int position, unsigned int end)
-{
- unsigned int c_chan;
-
- for (; position < end; ++position)
- for (c_chan = 0; c_chan < num_channels; c_chan++)
- *dest++ = buf[c_chan][position];
-}
-
-void
-flac_convert(void *dest,
- unsigned int num_channels, enum sample_format sample_format,
- const FLAC__int32 *const buf[],
- unsigned int position, unsigned int end)
-{
- switch (sample_format) {
- case SAMPLE_FORMAT_S16:
- if (num_channels == 2)
- flac_convert_stereo16((int16_t*)dest, buf,
- position, end);
- else
- flac_convert_16((int16_t*)dest, num_channels, buf,
- position, end);
- break;
-
- case SAMPLE_FORMAT_S24_P32:
- case SAMPLE_FORMAT_S32:
- flac_convert_32((int32_t*)dest, num_channels, buf,
- position, end);
- break;
-
- case SAMPLE_FORMAT_S8:
- flac_convert_8((int8_t*)dest, num_channels, buf,
- position, end);
- break;
-
- case SAMPLE_FORMAT_FLOAT:
- case SAMPLE_FORMAT_DSD:
- case SAMPLE_FORMAT_UNDEFINED:
- /* unreachable */
- assert(false);
- }
-}
diff --git a/src/decoder/flac_pcm.h b/src/decoder/flac_pcm.h
deleted file mode 100644
index a931998c1..000000000
--- a/src/decoder/flac_pcm.h
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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_FLAC_PCM_H
-#define MPD_FLAC_PCM_H
-
-#include "audio_format.h"
-
-#include <FLAC/ordinals.h>
-
-void
-flac_convert(void *dest,
- unsigned int num_channels, enum sample_format sample_format,
- const FLAC__int32 *const buf[],
- unsigned int position, unsigned int end);
-
-#endif
diff --git a/src/decoder/fluidsynth_decoder_plugin.c b/src/decoder/fluidsynth_decoder_plugin.c
deleted file mode 100644
index 894b2d353..000000000
--- a/src/decoder/fluidsynth_decoder_plugin.c
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
- * 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 "decoder_api.h"
-#include "audio_check.h"
-#include "conf.h"
-
-#include <glib.h>
-
-#include <fluidsynth.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "fluidsynth"
-
-static unsigned sample_rate;
-static const char *soundfont_path;
-
-/**
- * Convert a fluidsynth log level to a GLib log level.
- */
-static GLogLevelFlags
-fluidsynth_level_to_glib(enum fluid_log_level level)
-{
- switch (level) {
- case FLUID_PANIC:
- case FLUID_ERR:
- return G_LOG_LEVEL_CRITICAL;
-
- case FLUID_WARN:
- return G_LOG_LEVEL_WARNING;
-
- case FLUID_INFO:
- return G_LOG_LEVEL_INFO;
-
- case FLUID_DBG:
- case LAST_LOG_LEVEL:
- return G_LOG_LEVEL_DEBUG;
- }
-
- /* invalid fluidsynth log level */
- return G_LOG_LEVEL_MESSAGE;
-}
-
-/**
- * The fluidsynth logging callback. It forwards messages to the GLib
- * logging library.
- */
-static void
-fluidsynth_mpd_log_function(int level, char *message, G_GNUC_UNUSED void *data)
-{
- g_log(G_LOG_DOMAIN, fluidsynth_level_to_glib(level), "%s", message);
-}
-
-static bool
-fluidsynth_init(const struct config_param *param)
-{
- GError *error = NULL;
-
- sample_rate = config_get_block_unsigned(param, "sample_rate", 48000);
- if (!audio_check_sample_rate(sample_rate, &error)) {
- g_warning("%s\n", error->message);
- g_error_free(error);
- return false;
- }
-
- soundfont_path =
- config_get_block_string(param, "soundfont",
- "/usr/share/sounds/sf2/FluidR3_GM.sf2");
-
- fluid_set_log_function(LAST_LOG_LEVEL,
- fluidsynth_mpd_log_function, NULL);
-
- return true;
-}
-
-static void
-fluidsynth_file_decode(struct decoder *decoder, const char *path_fs)
-{
- char setting_sample_rate[] = "synth.sample-rate";
- /*
- char setting_verbose[] = "synth.verbose";
- char setting_yes[] = "yes";
- */
- fluid_settings_t *settings;
- fluid_synth_t *synth;
- fluid_player_t *player;
- int ret;
- enum decoder_command cmd;
-
- /* set up fluid settings */
-
- settings = new_fluid_settings();
- if (settings == NULL)
- return;
-
- fluid_settings_setnum(settings, setting_sample_rate, sample_rate);
-
- /*
- fluid_settings_setstr(settings, setting_verbose, setting_yes);
- */
-
- /* create the fluid synth */
-
- synth = new_fluid_synth(settings);
- if (synth == NULL) {
- delete_fluid_settings(settings);
- return;
- }
-
- ret = fluid_synth_sfload(synth, soundfont_path, true);
- if (ret < 0) {
- g_warning("fluid_synth_sfload() failed");
- delete_fluid_synth(synth);
- delete_fluid_settings(settings);
- return;
- }
-
- /* create the fluid player */
-
- player = new_fluid_player(synth);
- if (player == NULL) {
- delete_fluid_synth(synth);
- delete_fluid_settings(settings);
- return;
- }
-
- ret = fluid_player_add(player, path_fs);
- if (ret != 0) {
- g_warning("fluid_player_add() failed");
- delete_fluid_player(player);
- delete_fluid_synth(synth);
- delete_fluid_settings(settings);
- return;
- }
-
- /* start the player */
-
- ret = fluid_player_play(player);
- if (ret != 0) {
- g_warning("fluid_player_play() failed");
- delete_fluid_player(player);
- delete_fluid_synth(synth);
- delete_fluid_settings(settings);
- return;
- }
-
- /* initialization complete - announce the audio format to the
- MPD core */
-
- struct audio_format audio_format;
- audio_format_init(&audio_format, sample_rate, SAMPLE_FORMAT_S16, 2);
- decoder_initialized(decoder, &audio_format, false, -1);
-
- while (fluid_player_get_status(player) == FLUID_PLAYER_PLAYING) {
- int16_t buffer[2048];
- const unsigned max_frames = G_N_ELEMENTS(buffer) / 2;
-
- /* read samples from fluidsynth and send them to the
- MPD core */
-
- ret = fluid_synth_write_s16(synth, max_frames,
- buffer, 0, 2,
- buffer, 1, 2);
- if (ret != 0)
- break;
-
- cmd = decoder_data(decoder, NULL, buffer, sizeof(buffer),
- 0);
- if (cmd != DECODE_COMMAND_NONE)
- break;
- }
-
- /* clean up */
-
- fluid_player_stop(player);
- fluid_player_join(player);
-
- delete_fluid_player(player);
- delete_fluid_synth(synth);
- delete_fluid_settings(settings);
-}
-
-static bool
-fluidsynth_scan_file(const char *file,
- G_GNUC_UNUSED const struct tag_handler *handler,
- G_GNUC_UNUSED void *handler_ctx)
-{
- return fluid_is_midifile(file);
-}
-
-static const char *const fluidsynth_suffixes[] = {
- "mid",
- NULL
-};
-
-const struct decoder_plugin fluidsynth_decoder_plugin = {
- .name = "fluidsynth",
- .init = fluidsynth_init,
- .file_decode = fluidsynth_file_decode,
- .scan_file = fluidsynth_scan_file,
- .suffixes = fluidsynth_suffixes,
-};
diff --git a/src/decoder/gme_decoder_plugin.c b/src/decoder/gme_decoder_plugin.c
deleted file mode 100644
index 237a1deb1..000000000
--- a/src/decoder/gme_decoder_plugin.c
+++ /dev/null
@@ -1,257 +0,0 @@
-#include "config.h"
-#include "../decoder_api.h"
-#include "audio_check.h"
-#include "uri.h"
-#include "tag_handler.h"
-
-#include <glib.h>
-#include <assert.h>
-#include <errno.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include <gme/gme.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "gme"
-
-#define SUBTUNE_PREFIX "tune_"
-
-enum {
- GME_SAMPLE_RATE = 44100,
- GME_CHANNELS = 2,
- GME_BUFFER_FRAMES = 2048,
- GME_BUFFER_SAMPLES = GME_BUFFER_FRAMES * GME_CHANNELS,
-};
-
-/**
- * returns the file path stripped of any /tune_xxx.* subtune
- * suffix
- */
-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 *pat = g_strconcat("*/" SUBTUNE_PREFIX "???.", subtune_suffix, NULL);
- GPatternSpec *path_with_subtune = g_pattern_spec_new(pat);
- g_free(pat);
- if (!g_pattern_match(path_with_subtune,
- strlen(path_container), path_container, NULL)) {
- g_pattern_spec_free(path_with_subtune);
- return path_container;
- }
-
- char *ptr = g_strrstr(path_container, "/" SUBTUNE_PREFIX);
- if (ptr != NULL)
- *ptr='\0';
-
- g_pattern_spec_free(path_with_subtune);
- return path_container;
-}
-
-/**
- * returns tune number from file.nsf/tune_xxx.* style path or 0 if no subtune
- * is appended.
- */
-static int
-get_song_num(const char *path_fs)
-{
- const char *subtune_suffix = uri_get_suffix(path_fs);
- char *pat = g_strconcat("*/" SUBTUNE_PREFIX "???.", subtune_suffix, NULL);
- GPatternSpec *path_with_subtune = g_pattern_spec_new(pat);
- g_free(pat);
-
- if (g_pattern_match(path_with_subtune,
- strlen(path_fs), path_fs, NULL)) {
- char *sub = g_strrstr(path_fs, "/" SUBTUNE_PREFIX);
- g_pattern_spec_free(path_with_subtune);
- if(!sub)
- return 0;
-
- sub += strlen("/" SUBTUNE_PREFIX);
- int song_num = strtol(sub, NULL, 10);
-
- return song_num - 1;
- } else {
- g_pattern_spec_free(path_with_subtune);
- return 0;
- }
-}
-
-static char *
-gme_container_scan(const char *path_fs, const unsigned int tnum)
-{
- Music_Emu *emu;
- const char* gme_err;
- unsigned int num_songs;
-
- gme_err = gme_open_file(path_fs, &emu, GME_SAMPLE_RATE);
- if (gme_err != NULL) {
- g_warning("%s", gme_err);
- return NULL;
- }
-
- num_songs = gme_track_count(emu);
- /* if it only contains a single tune, don't treat as container */
- if (num_songs < 2)
- return NULL;
-
- const char *subtune_suffix = uri_get_suffix(path_fs);
- if (tnum <= num_songs){
- char *subtune = g_strdup_printf(
- SUBTUNE_PREFIX "%03u.%s", tnum, subtune_suffix);
- return subtune;
- } else
- return NULL;
-}
-
-static void
-gme_file_decode(struct decoder *decoder, const char *path_fs)
-{
- float song_len;
- Music_Emu *emu;
- gme_info_t *ti;
- struct audio_format audio_format;
- enum decoder_command cmd;
- short buf[GME_BUFFER_SAMPLES];
- const char* gme_err;
- char *path_container = get_container_name(path_fs);
- int song_num = get_song_num(path_fs);
-
- gme_err = gme_open_file(path_container, &emu, GME_SAMPLE_RATE);
- g_free(path_container);
- if (gme_err != NULL) {
- g_warning("%s", gme_err);
- return;
- }
-
- if((gme_err = gme_track_info(emu, &ti, song_num)) != NULL){
- g_warning("%s", gme_err);
- gme_delete(emu);
- return;
- }
-
- if(ti->length > 0)
- song_len = ti->length / 1000.0;
- else song_len = -1;
-
- /* initialize the MPD decoder */
-
- GError *error = NULL;
- if (!audio_format_init_checked(&audio_format, GME_SAMPLE_RATE,
- SAMPLE_FORMAT_S16, GME_CHANNELS,
- &error)) {
- g_warning("%s", error->message);
- g_error_free(error);
- gme_free_info(ti);
- gme_delete(emu);
- return;
- }
-
- decoder_initialized(decoder, &audio_format, true, song_len);
-
- if((gme_err = gme_start_track(emu, song_num)) != NULL)
- g_warning("%s", gme_err);
-
- if(ti->length > 0)
- gme_set_fade(emu, ti->length);
-
- /* play */
- do {
- gme_err = gme_play(emu, GME_BUFFER_SAMPLES, buf);
- if (gme_err != NULL) {
- g_warning("%s", gme_err);
- return;
- }
- cmd = decoder_data(decoder, NULL, buf, sizeof(buf), 0);
-
- if(cmd == DECODE_COMMAND_SEEK) {
- float where = decoder_seek_where(decoder);
- if((gme_err = gme_seek(emu, (int)where*1000)) != NULL)
- g_warning("%s", gme_err);
- decoder_command_finished(decoder);
- }
-
- if(gme_track_ended(emu))
- break;
- } while(cmd != DECODE_COMMAND_STOP);
-
- gme_free_info(ti);
- gme_delete(emu);
-}
-
-static bool
-gme_scan_file(const char *path_fs,
- const struct tag_handler *handler, void *handler_ctx)
-{
- Music_Emu *emu;
- gme_info_t *ti;
- const char* gme_err;
- char *path_container=get_container_name(path_fs);
- int song_num;
- song_num=get_song_num(path_fs);
-
- gme_err = gme_open_file(path_container, &emu, GME_SAMPLE_RATE);
- g_free(path_container);
- if (gme_err != NULL) {
- g_warning("%s", gme_err);
- return false;
- }
- if((gme_err = gme_track_info(emu, &ti, song_num)) != NULL){
- g_warning("%s", gme_err);
- gme_delete(emu);
- return false;
- }
-
- assert(ti != NULL);
-
- if(ti->length > 0)
- tag_handler_invoke_duration(handler, handler_ctx,
- ti->length / 100);
-
- if(ti->song != NULL){
- if(gme_track_count(emu) > 1){
- /* start numbering subtunes from 1 */
- char *tag_title=g_strdup_printf("%s (%d/%d)",
- ti->song, song_num+1, gme_track_count(emu));
- tag_handler_invoke_tag(handler, handler_ctx,
- TAG_TITLE, tag_title);
- g_free(tag_title);
- }else
- tag_handler_invoke_tag(handler, handler_ctx,
- TAG_TITLE, ti->song);
- }
- if(ti->author != NULL)
- tag_handler_invoke_tag(handler, handler_ctx,
- TAG_ARTIST, ti->author);
- if(ti->game != NULL)
- tag_handler_invoke_tag(handler, handler_ctx,
- TAG_ALBUM, ti->game);
- if(ti->comment != NULL)
- tag_handler_invoke_tag(handler, handler_ctx,
- TAG_COMMENT, ti->comment);
- if(ti->copyright != NULL)
- tag_handler_invoke_tag(handler, handler_ctx,
- TAG_DATE, ti->copyright);
-
- gme_free_info(ti);
- gme_delete(emu);
-
- return true;
-}
-
-static const char *const gme_suffixes[] = {
- "ay", "gbs", "gym", "hes", "kss", "nsf",
- "nsfe", "sap", "spc", "vgm", "vgz",
- NULL
-};
-
-extern const struct decoder_plugin gme_decoder_plugin;
-const struct decoder_plugin gme_decoder_plugin = {
- .name = "gme",
- .file_decode = gme_file_decode,
- .scan_file = gme_scan_file,
- .suffixes = gme_suffixes,
- .container_scan = gme_container_scan,
-};
diff --git a/src/decoder/mad_decoder_plugin.c b/src/decoder/mad_decoder_plugin.c
deleted file mode 100644
index 62c371642..000000000
--- a/src/decoder/mad_decoder_plugin.c
+++ /dev/null
@@ -1,1203 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "decoder_api.h"
-#include "conf.h"
-#include "tag_id3.h"
-#include "tag_rva2.h"
-#include "tag_handler.h"
-#include "audio_check.h"
-
-#include <assert.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <glib.h>
-#include <mad.h>
-
-#ifdef HAVE_ID3TAG
-#include <id3tag.h>
-#endif
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "mad"
-
-#define FRAMES_CUSHION 2000
-
-#define READ_BUFFER_SIZE 40960
-
-enum mp3_action {
- DECODE_SKIP = -3,
- DECODE_BREAK = -2,
- DECODE_CONT = -1,
- DECODE_OK = 0
-};
-
-enum muteframe {
- MUTEFRAME_NONE,
- MUTEFRAME_SKIP,
- MUTEFRAME_SEEK
-};
-
-/* the number of samples of silence the decoder inserts at start */
-#define DECODERDELAY 529
-
-#define DEFAULT_GAPLESS_MP3_PLAYBACK true
-
-static bool gapless_playback;
-
-static inline int32_t
-mad_fixed_to_24_sample(mad_fixed_t sample)
-{
- enum {
- bits = 24,
- MIN = -MAD_F_ONE,
- MAX = MAD_F_ONE - 1
- };
-
- /* round */
- sample = sample + (1L << (MAD_F_FRACBITS - bits));
-
- /* clip */
- if (sample > MAX)
- sample = MAX;
- else if (sample < MIN)
- sample = MIN;
-
- /* quantize */
- return sample >> (MAD_F_FRACBITS + 1 - bits);
-}
-
-static void
-mad_fixed_to_24_buffer(int32_t *dest, const struct mad_synth *synth,
- unsigned int start, unsigned int end,
- unsigned int num_channels)
-{
- unsigned int i, c;
-
- for (i = start; i < end; ++i) {
- for (c = 0; c < num_channels; ++c)
- *dest++ = mad_fixed_to_24_sample(synth->pcm.samples[c][i]);
- }
-}
-
-static bool
-mp3_plugin_init(G_GNUC_UNUSED const struct config_param *param)
-{
- gapless_playback = config_get_bool(CONF_GAPLESS_MP3_PLAYBACK,
- DEFAULT_GAPLESS_MP3_PLAYBACK);
- return true;
-}
-
-#define MP3_DATA_OUTPUT_BUFFER_SIZE 2048
-
-struct mp3_data {
- struct mad_stream stream;
- struct mad_frame frame;
- struct mad_synth synth;
- mad_timer_t timer;
- unsigned char input_buffer[READ_BUFFER_SIZE];
- int32_t output_buffer[MP3_DATA_OUTPUT_BUFFER_SIZE];
- float total_time;
- float elapsed_time;
- float seek_where;
- enum muteframe mute_frame;
- long *frame_offsets;
- mad_timer_t *times;
- unsigned long highest_frame;
- unsigned long max_frames;
- unsigned long current_frame;
- unsigned int drop_start_frames;
- unsigned int drop_end_frames;
- unsigned int drop_start_samples;
- unsigned int drop_end_samples;
- bool found_replay_gain;
- bool found_xing;
- bool found_first_frame;
- bool decoded_first_frame;
- unsigned long bit_rate;
- struct decoder *decoder;
- struct input_stream *input_stream;
- enum mad_layer layer;
-};
-
-static void
-mp3_data_init(struct mp3_data *data, struct decoder *decoder,
- struct input_stream *input_stream)
-{
- data->mute_frame = MUTEFRAME_NONE;
- data->highest_frame = 0;
- data->max_frames = 0;
- data->frame_offsets = NULL;
- data->times = NULL;
- data->current_frame = 0;
- data->drop_start_frames = 0;
- data->drop_end_frames = 0;
- data->drop_start_samples = 0;
- data->drop_end_samples = 0;
- data->found_replay_gain = false;
- data->found_xing = false;
- data->found_first_frame = false;
- data->decoded_first_frame = false;
- data->decoder = decoder;
- data->input_stream = input_stream;
- data->layer = 0;
-
- mad_stream_init(&data->stream);
- mad_stream_options(&data->stream, MAD_OPTION_IGNORECRC);
- mad_frame_init(&data->frame);
- mad_synth_init(&data->synth);
- mad_timer_reset(&data->timer);
-}
-
-static bool mp3_seek(struct mp3_data *data, long offset)
-{
- if (!input_stream_lock_seek(data->input_stream, offset, SEEK_SET, NULL))
- return false;
-
- mad_stream_buffer(&data->stream, data->input_buffer, 0);
- (data->stream).error = 0;
-
- return true;
-}
-
-static bool
-mp3_fill_buffer(struct mp3_data *data)
-{
- size_t remaining, length;
- unsigned char *dest;
-
- if (data->stream.next_frame != NULL) {
- remaining = data->stream.bufend - data->stream.next_frame;
- memmove(data->input_buffer, data->stream.next_frame,
- remaining);
- dest = (data->input_buffer) + remaining;
- length = READ_BUFFER_SIZE - remaining;
- } else {
- remaining = 0;
- length = READ_BUFFER_SIZE;
- dest = data->input_buffer;
- }
-
- /* we've exhausted the read buffer, so give up!, these potential
- * mp3 frames are way too big, and thus unlikely to be mp3 frames */
- if (length == 0)
- return false;
-
- length = decoder_read(data->decoder, data->input_stream, dest, length);
- if (length == 0)
- return false;
-
- mad_stream_buffer(&data->stream, data->input_buffer,
- length + remaining);
- (data->stream).error = 0;
-
- return true;
-}
-
-#ifdef HAVE_ID3TAG
-static bool
-parse_id3_replay_gain_info(struct replay_gain_info *replay_gain_info,
- struct id3_tag *tag)
-{
- int i;
- char *key;
- char *value;
- struct id3_frame *frame;
- bool found = false;
-
- replay_gain_info_init(replay_gain_info);
-
- for (i = 0; (frame = id3_tag_findframe(tag, "TXXX", i)); i++) {
- if (frame->nfields < 3)
- continue;
-
- key = (char *)
- id3_ucs4_latin1duplicate(id3_field_getstring
- (&frame->fields[1]));
- value = (char *)
- id3_ucs4_latin1duplicate(id3_field_getstring
- (&frame->fields[2]));
-
- if (g_ascii_strcasecmp(key, "replaygain_track_gain") == 0) {
- replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain = atof(value);
- found = true;
- } else if (g_ascii_strcasecmp(key, "replaygain_album_gain") == 0) {
- replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain = atof(value);
- found = true;
- } else if (g_ascii_strcasecmp(key, "replaygain_track_peak") == 0) {
- replay_gain_info->tuples[REPLAY_GAIN_TRACK].peak = atof(value);
- found = true;
- } else if (g_ascii_strcasecmp(key, "replaygain_album_peak") == 0) {
- replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak = atof(value);
- found = true;
- }
-
- free(key);
- free(value);
- }
-
- return found ||
- /* fall back on RVA2 if no replaygain tags found */
- tag_rva2_parse(tag, replay_gain_info);
-}
-#endif
-
-#ifdef HAVE_ID3TAG
-static bool
-parse_id3_mixramp(char **mixramp_start, char **mixramp_end,
- struct id3_tag *tag)
-{
- int i;
- char *key;
- char *value;
- struct id3_frame *frame;
- bool found = false;
-
- *mixramp_start = NULL;
- *mixramp_end = NULL;
-
- for (i = 0; (frame = id3_tag_findframe(tag, "TXXX", i)); i++) {
- if (frame->nfields < 3)
- continue;
-
- key = (char *)
- id3_ucs4_latin1duplicate(id3_field_getstring
- (&frame->fields[1]));
- value = (char *)
- id3_ucs4_latin1duplicate(id3_field_getstring
- (&frame->fields[2]));
-
- if (g_ascii_strcasecmp(key, "mixramp_start") == 0) {
- *mixramp_start = g_strdup(value);
- found = true;
- } else if (g_ascii_strcasecmp(key, "mixramp_end") == 0) {
- *mixramp_end = g_strdup(value);
- found = true;
- }
-
- free(key);
- free(value);
- }
-
- return found;
-}
-#endif
-
-static void mp3_parse_id3(struct mp3_data *data, size_t tagsize,
- struct tag **mpd_tag)
-{
-#ifdef HAVE_ID3TAG
- struct id3_tag *id3_tag = NULL;
- id3_length_t count;
- id3_byte_t const *id3_data;
- id3_byte_t *allocated = NULL;
-
- count = data->stream.bufend - data->stream.this_frame;
-
- if (tagsize <= count) {
- id3_data = data->stream.this_frame;
- mad_stream_skip(&(data->stream), tagsize);
- } else {
- allocated = g_malloc(tagsize);
- memcpy(allocated, data->stream.this_frame, count);
- mad_stream_skip(&(data->stream), count);
-
- while (count < tagsize) {
- size_t len;
-
- len = decoder_read(data->decoder, data->input_stream,
- allocated + count, tagsize - count);
- if (len == 0)
- break;
- else
- count += len;
- }
-
- if (count != tagsize) {
- g_debug("error parsing ID3 tag");
- g_free(allocated);
- return;
- }
-
- id3_data = allocated;
- }
-
- id3_tag = id3_tag_parse(id3_data, tagsize);
- if (id3_tag == NULL) {
- g_free(allocated);
- return;
- }
-
- if (mpd_tag) {
- struct tag *tmp_tag = tag_id3_import(id3_tag);
- if (tmp_tag != NULL) {
- if (*mpd_tag != NULL)
- tag_free(*mpd_tag);
- *mpd_tag = tmp_tag;
- }
- }
-
- if (data->decoder != NULL) {
- struct replay_gain_info rgi;
- char *mixramp_start;
- char *mixramp_end;
- float replay_gain_db = 0;
-
- if (parse_id3_replay_gain_info(&rgi, id3_tag)) {
- replay_gain_db = decoder_replay_gain(data->decoder, &rgi);
- data->found_replay_gain = true;
- }
-
- if (parse_id3_mixramp(&mixramp_start, &mixramp_end, id3_tag))
- decoder_mixramp(data->decoder, replay_gain_db,
- mixramp_start, mixramp_end);
- }
-
- id3_tag_delete(id3_tag);
-
- g_free(allocated);
-#else /* !HAVE_ID3TAG */
- (void)mpd_tag;
-
- /* This code is enabled when libid3tag is disabled. Instead
- of parsing the ID3 frame, it just skips it. */
-
- size_t count = data->stream.bufend - data->stream.this_frame;
-
- if (tagsize <= count) {
- mad_stream_skip(&data->stream, tagsize);
- } else {
- mad_stream_skip(&data->stream, count);
-
- while (count < tagsize) {
- size_t len = tagsize - count;
- char ignored[1024];
- if (len > sizeof(ignored))
- len = sizeof(ignored);
-
- len = decoder_read(data->decoder, data->input_stream,
- ignored, len);
- if (len == 0)
- break;
- else
- count += len;
- }
- }
-#endif
-}
-
-#ifndef HAVE_ID3TAG
-/**
- * This function emulates libid3tag when it is disabled. Instead of
- * doing a real analyzation of the frame, it just checks whether the
- * frame begins with the string "ID3". If so, it returns the length
- * of the ID3 frame.
- */
-static signed long
-id3_tag_query(const void *p0, size_t length)
-{
- const char *p = p0;
-
- return length >= 10 && memcmp(p, "ID3", 3) == 0
- ? (p[8] << 7) + p[9] + 10
- : 0;
-}
-#endif /* !HAVE_ID3TAG */
-
-static enum mp3_action
-decode_next_frame_header(struct mp3_data *data, G_GNUC_UNUSED struct tag **tag)
-{
- enum mad_layer layer;
-
- if ((data->stream).buffer == NULL
- || (data->stream).error == MAD_ERROR_BUFLEN) {
- if (!mp3_fill_buffer(data))
- return DECODE_BREAK;
- }
- if (mad_header_decode(&data->frame.header, &data->stream)) {
- if ((data->stream).error == MAD_ERROR_LOSTSYNC &&
- (data->stream).this_frame) {
- signed long tagsize = id3_tag_query((data->stream).
- this_frame,
- (data->stream).
- bufend -
- (data->stream).
- this_frame);
-
- if (tagsize > 0) {
- if (tag && !(*tag)) {
- mp3_parse_id3(data, (size_t)tagsize,
- tag);
- } else {
- mad_stream_skip(&(data->stream),
- tagsize);
- }
- return DECODE_CONT;
- }
- }
- if (MAD_RECOVERABLE((data->stream).error)) {
- return DECODE_SKIP;
- } else {
- if ((data->stream).error == MAD_ERROR_BUFLEN)
- return DECODE_CONT;
- else {
- g_warning("unrecoverable frame level error "
- "(%s).\n",
- mad_stream_errorstr(&data->stream));
- return DECODE_BREAK;
- }
- }
- }
-
- layer = data->frame.header.layer;
- if (!data->layer) {
- if (layer != MAD_LAYER_II && layer != MAD_LAYER_III) {
- /* Only layer 2 and 3 have been tested to work */
- return DECODE_SKIP;
- }
- data->layer = layer;
- } else if (layer != data->layer) {
- /* Don't decode frames with a different layer than the first */
- return DECODE_SKIP;
- }
-
- return DECODE_OK;
-}
-
-static enum mp3_action
-decodeNextFrame(struct mp3_data *data)
-{
- if ((data->stream).buffer == NULL
- || (data->stream).error == MAD_ERROR_BUFLEN) {
- if (!mp3_fill_buffer(data))
- return DECODE_BREAK;
- }
- if (mad_frame_decode(&data->frame, &data->stream)) {
- if ((data->stream).error == MAD_ERROR_LOSTSYNC) {
- signed long tagsize = id3_tag_query((data->stream).
- this_frame,
- (data->stream).
- bufend -
- (data->stream).
- this_frame);
- if (tagsize > 0) {
- mad_stream_skip(&(data->stream), tagsize);
- return DECODE_CONT;
- }
- }
- if (MAD_RECOVERABLE((data->stream).error)) {
- return DECODE_SKIP;
- } else {
- if ((data->stream).error == MAD_ERROR_BUFLEN)
- return DECODE_CONT;
- else {
- g_warning("unrecoverable frame level error "
- "(%s).\n",
- mad_stream_errorstr(&data->stream));
- return DECODE_BREAK;
- }
- }
- }
-
- return DECODE_OK;
-}
-
-/* xing stuff stolen from alsaplayer, and heavily modified by jat */
-#define XI_MAGIC (('X' << 8) | 'i')
-#define NG_MAGIC (('n' << 8) | 'g')
-#define IN_MAGIC (('I' << 8) | 'n')
-#define FO_MAGIC (('f' << 8) | 'o')
-
-enum xing_magic {
- XING_MAGIC_XING, /* VBR */
- XING_MAGIC_INFO /* CBR */
-};
-
-struct xing {
- long flags; /* valid fields (see below) */
- unsigned long frames; /* total number of frames */
- unsigned long bytes; /* total number of bytes */
- unsigned char toc[100]; /* 100-point seek table */
- long scale; /* VBR quality */
- enum xing_magic magic; /* header magic */
-};
-
-enum {
- XING_FRAMES = 0x00000001L,
- XING_BYTES = 0x00000002L,
- XING_TOC = 0x00000004L,
- XING_SCALE = 0x00000008L
-};
-
-struct lame_version {
- unsigned major;
- unsigned minor;
-};
-
-struct lame {
- char encoder[10]; /* 9 byte encoder name/version ("LAME3.97b") */
- struct lame_version version; /* struct containing just the version */
- float peak; /* replaygain peak */
- float track_gain; /* replaygain track gain */
- float album_gain; /* replaygain album gain */
- int encoder_delay; /* # of added samples at start of mp3 */
- int encoder_padding; /* # of added samples at end of mp3 */
- int crc; /* CRC of the first 190 bytes of this frame */
-};
-
-static bool
-parse_xing(struct xing *xing, struct mad_bitptr *ptr, int *oldbitlen)
-{
- unsigned long bits;
- int bitlen;
- int bitsleft;
- int i;
-
- bitlen = *oldbitlen;
-
- if (bitlen < 16)
- return false;
-
- bits = mad_bit_read(ptr, 16);
- bitlen -= 16;
-
- if (bits == XI_MAGIC) {
- if (bitlen < 16)
- return false;
-
- if (mad_bit_read(ptr, 16) != NG_MAGIC)
- return false;
-
- bitlen -= 16;
- xing->magic = XING_MAGIC_XING;
- } else if (bits == IN_MAGIC) {
- if (bitlen < 16)
- return false;
-
- if (mad_bit_read(ptr, 16) != FO_MAGIC)
- return false;
-
- bitlen -= 16;
- xing->magic = XING_MAGIC_INFO;
- }
- else if (bits == NG_MAGIC) xing->magic = XING_MAGIC_XING;
- else if (bits == FO_MAGIC) xing->magic = XING_MAGIC_INFO;
- else
- return false;
-
- if (bitlen < 32)
- return false;
- xing->flags = mad_bit_read(ptr, 32);
- bitlen -= 32;
-
- if (xing->flags & XING_FRAMES) {
- if (bitlen < 32)
- return false;
- xing->frames = mad_bit_read(ptr, 32);
- bitlen -= 32;
- }
-
- if (xing->flags & XING_BYTES) {
- if (bitlen < 32)
- return false;
- xing->bytes = mad_bit_read(ptr, 32);
- bitlen -= 32;
- }
-
- if (xing->flags & XING_TOC) {
- if (bitlen < 800)
- return false;
- for (i = 0; i < 100; ++i) xing->toc[i] = mad_bit_read(ptr, 8);
- bitlen -= 800;
- }
-
- if (xing->flags & XING_SCALE) {
- if (bitlen < 32)
- return false;
- xing->scale = mad_bit_read(ptr, 32);
- bitlen -= 32;
- }
-
- /* Make sure we consume no less than 120 bytes (960 bits) in hopes that
- * the LAME tag is found there, and not right after the Xing header */
- bitsleft = 960 - ((*oldbitlen) - bitlen);
- if (bitsleft < 0)
- return false;
- else if (bitsleft > 0) {
- mad_bit_read(ptr, bitsleft);
- bitlen -= bitsleft;
- }
-
- *oldbitlen = bitlen;
-
- return true;
-}
-
-static bool
-parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen)
-{
- int adj = 0;
- int name;
- int orig;
- int sign;
- int gain;
- int i;
-
- /* Unlike the xing header, the lame tag has a fixed length. Fail if
- * not all 36 bytes (288 bits) are there. */
- if (*bitlen < 288)
- return false;
-
- for (i = 0; i < 9; i++)
- lame->encoder[i] = (char)mad_bit_read(ptr, 8);
- lame->encoder[9] = '\0';
-
- *bitlen -= 72;
-
- /* 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"))
- return false;
-
- if (sscanf(lame->encoder+4, "%u.%u",
- &lame->version.major, &lame->version.minor) != 2)
- return false;
-
- g_debug("detected LAME version %i.%i (\"%s\")\n",
- lame->version.major, lame->version.minor, lame->encoder);
-
- /* The reference volume was changed from the 83dB used in the
- * ReplayGain spec to 89dB in lame 3.95.1. Bump the gain for older
- * versions, since everyone else uses 89dB instead of 83dB.
- * Unfortunately, lame didn't differentiate between 3.95 and 3.95.1, so
- * it's impossible to make the proper adjustment for 3.95.
- * Fortunately, 3.95 was only out for about a day before 3.95.1 was
- * released. -- tmz */
- if (lame->version.major < 3 ||
- (lame->version.major == 3 && lame->version.minor < 95))
- adj = 6;
-
- mad_bit_read(ptr, 16);
-
- lame->peak = mad_f_todouble(mad_bit_read(ptr, 32) << 5); /* peak */
- g_debug("LAME peak found: %f\n", lame->peak);
-
- lame->track_gain = 0;
- name = mad_bit_read(ptr, 3); /* gain name */
- orig = mad_bit_read(ptr, 3); /* gain originator */
- sign = mad_bit_read(ptr, 1); /* sign bit */
- gain = mad_bit_read(ptr, 9); /* gain*10 */
- if (gain && name == 1 && orig != 0) {
- lame->track_gain = ((sign ? -gain : gain) / 10.0) + adj;
- g_debug("LAME track gain found: %f\n", lame->track_gain);
- }
-
- /* tmz reports that this isn't currently written by any version of lame
- * (as of 3.97). Since we have no way of testing it, don't use it.
- * Wouldn't want to go blowing someone's ears just because we read it
- * wrong. :P -- jat */
- lame->album_gain = 0;
-#if 0
- name = mad_bit_read(ptr, 3); /* gain name */
- orig = mad_bit_read(ptr, 3); /* gain originator */
- sign = mad_bit_read(ptr, 1); /* sign bit */
- gain = mad_bit_read(ptr, 9); /* gain*10 */
- if (gain && name == 2 && orig != 0) {
- lame->album_gain = ((sign ? -gain : gain) / 10.0) + adj;
- g_debug("LAME album gain found: %f\n", lame->track_gain);
- }
-#else
- mad_bit_read(ptr, 16);
-#endif
-
- mad_bit_read(ptr, 16);
-
- lame->encoder_delay = mad_bit_read(ptr, 12);
- lame->encoder_padding = mad_bit_read(ptr, 12);
-
- g_debug("encoder delay is %i, encoder padding is %i\n",
- lame->encoder_delay, lame->encoder_padding);
-
- mad_bit_read(ptr, 80);
-
- lame->crc = mad_bit_read(ptr, 16);
-
- *bitlen -= 216;
-
- return true;
-}
-
-static inline float
-mp3_frame_duration(const struct mad_frame *frame)
-{
- return mad_timer_count(frame->header.duration,
- MAD_UNITS_MILLISECONDS) / 1000.0;
-}
-
-static goffset
-mp3_this_frame_offset(const struct mp3_data *data)
-{
- goffset offset = data->input_stream->offset;
-
- if (data->stream.this_frame != NULL)
- offset -= data->stream.bufend - data->stream.this_frame;
- else
- offset -= data->stream.bufend - data->stream.buffer;
-
- return offset;
-}
-
-static goffset
-mp3_rest_including_this_frame(const struct mp3_data *data)
-{
- return data->input_stream->size - mp3_this_frame_offset(data);
-}
-
-/**
- * Attempt to calulcate the length of the song from filesize
- */
-static void
-mp3_filesize_to_song_length(struct mp3_data *data)
-{
- goffset rest = mp3_rest_including_this_frame(data);
-
- if (rest > 0) {
- float frame_duration = mp3_frame_duration(&data->frame);
-
- data->total_time = (rest * 8.0) / (data->frame).header.bitrate;
- data->max_frames = data->total_time / frame_duration +
- FRAMES_CUSHION;
- } else {
- data->max_frames = FRAMES_CUSHION;
- data->total_time = 0;
- }
-}
-
-static bool
-mp3_decode_first_frame(struct mp3_data *data, struct tag **tag)
-{
- struct xing xing;
- struct lame lame;
- struct mad_bitptr ptr;
- int bitlen;
- enum mp3_action ret;
-
- /* stfu gcc */
- memset(&xing, 0, sizeof(struct xing));
- xing.flags = 0;
-
- while (true) {
- do {
- ret = decode_next_frame_header(data, tag);
- } while (ret == DECODE_CONT);
- if (ret == DECODE_BREAK)
- return false;
- if (ret == DECODE_SKIP) continue;
-
- do {
- ret = decodeNextFrame(data);
- } while (ret == DECODE_CONT);
- if (ret == DECODE_BREAK)
- return false;
- if (ret == DECODE_OK) break;
- }
-
- ptr = data->stream.anc_ptr;
- bitlen = data->stream.anc_bitlen;
-
- mp3_filesize_to_song_length(data);
-
- /*
- * if an xing tag exists, use that!
- */
- if (parse_xing(&xing, &ptr, &bitlen)) {
- data->found_xing = true;
- data->mute_frame = MUTEFRAME_SKIP;
-
- if ((xing.flags & XING_FRAMES) && xing.frames) {
- mad_timer_t duration = data->frame.header.duration;
- mad_timer_multiply(&duration, xing.frames);
- data->total_time = ((float)mad_timer_count(duration, MAD_UNITS_MILLISECONDS)) / 1000;
- data->max_frames = xing.frames;
- }
-
- if (parse_lame(&lame, &ptr, &bitlen)) {
- if (gapless_playback &&
- data->input_stream->seekable) {
- data->drop_start_samples = lame.encoder_delay +
- DECODERDELAY;
- data->drop_end_samples = lame.encoder_padding;
- }
-
- /* Album gain isn't currently used. See comment in
- * parse_lame() for details. -- jat */
- if (data->decoder != NULL &&
- !data->found_replay_gain &&
- lame.track_gain) {
- struct replay_gain_info rgi;
- replay_gain_info_init(&rgi);
- rgi.tuples[REPLAY_GAIN_TRACK].gain = lame.track_gain;
- rgi.tuples[REPLAY_GAIN_TRACK].peak = lame.peak;
- decoder_replay_gain(data->decoder, &rgi);
- }
- }
- }
-
- if (!data->max_frames)
- return false;
-
- if (data->max_frames > 8 * 1024 * 1024) {
- g_warning("mp3 file header indicates too many frames: %lu\n",
- data->max_frames);
- return false;
- }
-
- data->frame_offsets = g_malloc(sizeof(long) * data->max_frames);
- data->times = g_malloc(sizeof(mad_timer_t) * data->max_frames);
-
- return true;
-}
-
-static void mp3_data_finish(struct mp3_data *data)
-{
- mad_synth_finish(&data->synth);
- mad_frame_finish(&data->frame);
- mad_stream_finish(&data->stream);
-
- g_free(data->frame_offsets);
- g_free(data->times);
-}
-
-/* this is primarily used for getting total time for tags */
-static int
-mad_decoder_total_file_time(struct input_stream *is)
-{
- struct mp3_data data;
- int ret;
-
- mp3_data_init(&data, NULL, is);
- if (!mp3_decode_first_frame(&data, NULL))
- ret = -1;
- else
- ret = data.total_time + 0.5;
- mp3_data_finish(&data);
-
- return ret;
-}
-
-static bool
-mp3_open(struct input_stream *is, struct mp3_data *data,
- struct decoder *decoder, struct tag **tag)
-{
- mp3_data_init(data, decoder, is);
- *tag = NULL;
- if (!mp3_decode_first_frame(data, tag)) {
- mp3_data_finish(data);
- if (tag && *tag)
- tag_free(*tag);
- return false;
- }
-
- return true;
-}
-
-static long
-mp3_time_to_frame(const struct mp3_data *data, double t)
-{
- unsigned long i;
-
- for (i = 0; i < data->highest_frame; ++i) {
- double frame_time =
- mad_timer_count(data->times[i],
- MAD_UNITS_MILLISECONDS) / 1000.;
- if (frame_time >= t)
- break;
- }
-
- return i;
-}
-
-static void
-mp3_update_timer_next_frame(struct mp3_data *data)
-{
- if (data->current_frame >= data->highest_frame) {
- /* record this frame's properties in
- data->frame_offsets (for seeking) and
- data->times */
- data->bit_rate = (data->frame).header.bitrate;
-
- if (data->current_frame >= data->max_frames)
- /* cap data->current_frame */
- data->current_frame = data->max_frames - 1;
- else
- data->highest_frame++;
-
- data->frame_offsets[data->current_frame] =
- mp3_this_frame_offset(data);
-
- mad_timer_add(&data->timer, (data->frame).header.duration);
- data->times[data->current_frame] = data->timer;
- } else
- /* get the new timer value from data->times */
- data->timer = data->times[data->current_frame];
-
- data->current_frame++;
- data->elapsed_time =
- mad_timer_count(data->timer, MAD_UNITS_MILLISECONDS) / 1000.0;
-}
-
-/**
- * Sends the synthesized current frame via decoder_data().
- */
-static enum decoder_command
-mp3_send_pcm(struct mp3_data *data, unsigned i, unsigned pcm_length)
-{
- unsigned max_samples;
-
- max_samples = sizeof(data->output_buffer) /
- sizeof(data->output_buffer[0]) /
- MAD_NCHANNELS(&(data->frame).header);
-
- while (i < pcm_length) {
- enum decoder_command cmd;
- unsigned int num_samples = pcm_length - i;
- if (num_samples > max_samples)
- num_samples = max_samples;
-
- i += num_samples;
-
- mad_fixed_to_24_buffer(data->output_buffer,
- &data->synth,
- i - num_samples, i,
- MAD_NCHANNELS(&(data->frame).header));
- num_samples *= MAD_NCHANNELS(&(data->frame).header);
-
- cmd = decoder_data(data->decoder, data->input_stream,
- data->output_buffer,
- sizeof(data->output_buffer[0]) * num_samples,
- data->bit_rate / 1000);
- if (cmd != DECODE_COMMAND_NONE)
- return cmd;
- }
-
- return DECODE_COMMAND_NONE;
-}
-
-/**
- * Synthesize the current frame and send it via decoder_data().
- */
-static enum decoder_command
-mp3_synth_and_send(struct mp3_data *data)
-{
- unsigned i, pcm_length;
- enum decoder_command cmd;
-
- mad_synth_frame(&data->synth, &data->frame);
-
- if (!data->found_first_frame) {
- unsigned int samples_per_frame = data->synth.pcm.length;
- data->drop_start_frames = data->drop_start_samples / samples_per_frame;
- data->drop_end_frames = data->drop_end_samples / samples_per_frame;
- data->drop_start_samples = data->drop_start_samples % samples_per_frame;
- data->drop_end_samples = data->drop_end_samples % samples_per_frame;
- data->found_first_frame = true;
- }
-
- if (data->drop_start_frames > 0) {
- data->drop_start_frames--;
- return DECODE_COMMAND_NONE;
- } else if ((data->drop_end_frames > 0) &&
- (data->current_frame == (data->max_frames + 1 - data->drop_end_frames))) {
- /* stop decoding, effectively dropping all remaining
- frames */
- return DECODE_COMMAND_STOP;
- }
-
- if (!data->decoded_first_frame) {
- i = data->drop_start_samples;
- data->decoded_first_frame = true;
- } else
- i = 0;
-
- pcm_length = data->synth.pcm.length;
- if (data->drop_end_samples &&
- (data->current_frame == data->max_frames - data->drop_end_frames)) {
- if (data->drop_end_samples >= pcm_length)
- pcm_length = 0;
- else
- pcm_length -= data->drop_end_samples;
- }
-
- cmd = mp3_send_pcm(data, i, pcm_length);
- if (cmd != DECODE_COMMAND_NONE)
- return cmd;
-
- if (data->drop_end_samples &&
- (data->current_frame == data->max_frames - data->drop_end_frames))
- /* stop decoding, effectively dropping
- * all remaining samples */
- return DECODE_COMMAND_STOP;
-
- return DECODE_COMMAND_NONE;
-}
-
-static bool
-mp3_read(struct mp3_data *data)
-{
- struct decoder *decoder = data->decoder;
- enum mp3_action ret;
- enum decoder_command cmd;
-
- mp3_update_timer_next_frame(data);
-
- switch (data->mute_frame) {
- case MUTEFRAME_SKIP:
- data->mute_frame = MUTEFRAME_NONE;
- break;
- case MUTEFRAME_SEEK:
- if (data->elapsed_time >= data->seek_where)
- data->mute_frame = MUTEFRAME_NONE;
- break;
- case MUTEFRAME_NONE:
- cmd = mp3_synth_and_send(data);
- if (cmd == DECODE_COMMAND_SEEK) {
- unsigned long j;
-
- assert(data->input_stream->seekable);
-
- j = mp3_time_to_frame(data,
- decoder_seek_where(decoder));
- if (j < data->highest_frame) {
- if (mp3_seek(data, data->frame_offsets[j])) {
- data->current_frame = j;
- decoder_command_finished(decoder);
- } else
- decoder_seek_error(decoder);
- } else {
- data->seek_where = decoder_seek_where(decoder);
- data->mute_frame = MUTEFRAME_SEEK;
- decoder_command_finished(decoder);
- }
- } else if (cmd != DECODE_COMMAND_NONE)
- return false;
- }
-
- while (true) {
- bool skip = false;
-
- do {
- struct tag *tag = NULL;
-
- ret = decode_next_frame_header(data, &tag);
-
- if (tag != NULL) {
- decoder_tag(decoder, data->input_stream, tag);
- tag_free(tag);
- }
- } while (ret == DECODE_CONT);
- if (ret == DECODE_BREAK)
- return false;
- else if (ret == DECODE_SKIP)
- skip = true;
-
- if (data->mute_frame == MUTEFRAME_NONE) {
- do {
- ret = decodeNextFrame(data);
- } while (ret == DECODE_CONT);
- if (ret == DECODE_BREAK)
- return false;
- }
-
- if (!skip && ret == DECODE_OK)
- break;
- }
-
- return ret != DECODE_BREAK;
-}
-
-static void
-mp3_decode(struct decoder *decoder, struct input_stream *input_stream)
-{
- struct mp3_data data;
- GError *error = NULL;
- struct tag *tag = NULL;
- struct audio_format audio_format;
-
- if (!mp3_open(input_stream, &data, decoder, &tag)) {
- if (decoder_get_command(decoder) == DECODE_COMMAND_NONE)
- g_warning
- ("Input does not appear to be a mp3 bit stream.\n");
- return;
- }
-
- if (!audio_format_init_checked(&audio_format,
- data.frame.header.samplerate,
- SAMPLE_FORMAT_S24_P32,
- MAD_NCHANNELS(&data.frame.header),
- &error)) {
- g_warning("%s", error->message);
- g_error_free(error);
-
- if (tag != NULL)
- tag_free(tag);
- mp3_data_finish(&data);
- return;
- }
-
- decoder_initialized(decoder, &audio_format,
- data.input_stream->seekable, data.total_time);
-
- if (tag != NULL) {
- decoder_tag(decoder, input_stream, tag);
- tag_free(tag);
- }
-
- while (mp3_read(&data)) ;
-
- mp3_data_finish(&data);
-}
-
-static bool
-mad_decoder_scan_stream(struct input_stream *is,
- const struct tag_handler *handler, void *handler_ctx)
-{
- int total_time;
-
- total_time = mad_decoder_total_file_time(is);
- if (total_time < 0)
- return false;
-
- tag_handler_invoke_duration(handler, handler_ctx, total_time);
- return true;
-}
-
-static const char *const mp3_suffixes[] = { "mp3", "mp2", NULL };
-static const char *const mp3_mime_types[] = { "audio/mpeg", NULL };
-
-const struct decoder_plugin mad_decoder_plugin = {
- .name = "mad",
- .init = mp3_plugin_init,
- .stream_decode = mp3_decode,
- .scan_stream = mad_decoder_scan_stream,
- .suffixes = mp3_suffixes,
- .mime_types = mp3_mime_types
-};
diff --git a/src/decoder/mikmod_decoder_plugin.c b/src/decoder/mikmod_decoder_plugin.c
deleted file mode 100644
index a8fe818de..000000000
--- a/src/decoder/mikmod_decoder_plugin.c
+++ /dev/null
@@ -1,239 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "decoder_api.h"
-#include "mpd_error.h"
-#include "tag_handler.h"
-
-#include <glib.h>
-#include <mikmod.h>
-#include <assert.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "mikmod"
-
-/* this is largely copied from alsaplayer */
-
-#define MIKMOD_FRAME_SIZE 4096
-
-static BOOL
-mikmod_mpd_init(void)
-{
- return VC_Init();
-}
-
-static void
-mikmod_mpd_exit(void)
-{
- VC_Exit();
-}
-
-static void
-mikmod_mpd_update(void)
-{
-}
-
-static BOOL
-mikmod_mpd_is_present(void)
-{
- return true;
-}
-
-static char drv_name[] = PACKAGE_NAME;
-static char drv_version[] = VERSION;
-
-#if (LIBMIKMOD_VERSION > 0x030106)
-static char drv_alias[] = PACKAGE;
-#endif
-
-static MDRIVER drv_mpd = {
- NULL,
- drv_name,
- drv_version,
- 0,
- 255,
-#if (LIBMIKMOD_VERSION > 0x030106)
- drv_alias,
-#if (LIBMIKMOD_VERSION >= 0x030200)
- NULL, /* CmdLineHelp */
-#endif
- NULL, /* CommandLine */
-#endif
- mikmod_mpd_is_present,
- VC_SampleLoad,
- VC_SampleUnload,
- VC_SampleSpace,
- VC_SampleLength,
- mikmod_mpd_init,
- mikmod_mpd_exit,
- NULL,
- VC_SetNumVoices,
- VC_PlayStart,
- VC_PlayStop,
- mikmod_mpd_update,
- NULL,
- VC_VoiceSetVolume,
- VC_VoiceGetVolume,
- VC_VoiceSetFrequency,
- VC_VoiceGetFrequency,
- VC_VoiceSetPanning,
- VC_VoiceGetPanning,
- VC_VoicePlay,
- VC_VoiceStop,
- VC_VoiceStopped,
- VC_VoiceGetPosition,
- VC_VoiceRealVolume
-};
-
-static unsigned mikmod_sample_rate;
-
-static bool
-mikmod_decoder_init(const struct config_param *param)
-{
- static char params[] = "";
-
- mikmod_sample_rate = config_get_block_unsigned(param, "sample_rate",
- 44100);
- if (!audio_valid_sample_rate(mikmod_sample_rate))
- MPD_ERROR("Invalid sample rate in line %d: %u",
- param->line, mikmod_sample_rate);
-
- md_device = 0;
- md_reverb = 0;
-
- MikMod_RegisterDriver(&drv_mpd);
- MikMod_RegisterAllLoaders();
-
- md_pansep = 64;
- md_mixfreq = mikmod_sample_rate;
- md_mode = (DMODE_SOFT_MUSIC | DMODE_INTERP | DMODE_STEREO |
- DMODE_16BITS);
-
- if (MikMod_Init(params)) {
- g_warning("Could not init MikMod: %s\n",
- MikMod_strerror(MikMod_errno));
- return false;
- }
-
- return true;
-}
-
-static void
-mikmod_decoder_finish(void)
-{
- MikMod_Exit();
-}
-
-static void
-mikmod_decoder_file_decode(struct decoder *decoder, const char *path_fs)
-{
- char *path2;
- MODULE *handle;
- struct audio_format audio_format;
- int ret;
- SBYTE buffer[MIKMOD_FRAME_SIZE];
- enum decoder_command cmd = DECODE_COMMAND_NONE;
-
- path2 = g_strdup(path_fs);
- handle = Player_Load(path2, 128, 0);
- g_free(path2);
-
- if (handle == NULL) {
- g_warning("failed to open mod: %s", path_fs);
- return;
- }
-
- /* Prevent module from looping forever */
- handle->loop = 0;
-
- audio_format_init(&audio_format, mikmod_sample_rate, SAMPLE_FORMAT_S16, 2);
- assert(audio_format_valid(&audio_format));
-
- decoder_initialized(decoder, &audio_format, false, 0);
-
- Player_Start(handle);
- while (cmd == DECODE_COMMAND_NONE && Player_Active()) {
- ret = VC_WriteBytes(buffer, sizeof(buffer));
- cmd = decoder_data(decoder, NULL, buffer, ret, 0);
- }
-
- Player_Stop();
- Player_Free(handle);
-}
-
-static bool
-mikmod_decoder_scan_file(const char *path_fs,
- const struct tag_handler *handler, void *handler_ctx)
-{
- char *path2 = g_strdup(path_fs);
- MODULE *handle = Player_Load(path2, 128, 0);
-
- if (handle == NULL) {
- g_free(path2);
- g_debug("Failed to open file: %s", path_fs);
- return false;
-
- }
-
- Player_Free(handle);
-
- char *title = Player_LoadTitle(path2);
- g_free(path2);
-
- if (title != NULL) {
- tag_handler_invoke_tag(handler, handler_ctx,
- TAG_TITLE, title);
-#if (LIBMIKMOD_VERSION >= 0x030200)
- MikMod_free(title);
-#else
- free(title);
-#endif
- }
-
- return true;
-}
-
-static const char *const mikmod_decoder_suffixes[] = {
- "amf",
- "dsm",
- "far",
- "gdm",
- "imf",
- "it",
- "med",
- "mod",
- "mtm",
- "s3m",
- "stm",
- "stx",
- "ult",
- "uni",
- "xm",
- NULL
-};
-
-const struct decoder_plugin mikmod_decoder_plugin = {
- .name = "mikmod",
- .init = mikmod_decoder_init,
- .finish = mikmod_decoder_finish,
- .file_decode = mikmod_decoder_file_decode,
- .scan_file = mikmod_decoder_scan_file,
- .suffixes = mikmod_decoder_suffixes,
-};
diff --git a/src/decoder/modplug_decoder_plugin.c b/src/decoder/modplug_decoder_plugin.c
deleted file mode 100644
index 21ee79e7e..000000000
--- a/src/decoder/modplug_decoder_plugin.c
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "decoder_api.h"
-#include "tag_handler.h"
-
-#include <glib.h>
-#include <modplug.h>
-#include <assert.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "modplug"
-
-enum {
- MODPLUG_FRAME_SIZE = 4096,
- MODPLUG_PREALLOC_BLOCK = 256 * 1024,
- MODPLUG_READ_BLOCK = 128 * 1024,
- MODPLUG_FILE_LIMIT = 100 * 1024 * 1024,
-};
-
-static GByteArray *mod_loadfile(struct decoder *decoder, struct input_stream *is)
-{
- unsigned char *data;
- GByteArray *bdatas;
- size_t ret;
-
- if (is->size == 0) {
- g_warning("file is empty");
- return NULL;
- }
-
- if (is->size > MODPLUG_FILE_LIMIT) {
- g_warning("file too large");
- return NULL;
- }
-
- //known/unknown size, preallocate array, lets read in chunks
- if (is->size > 0) {
- bdatas = g_byte_array_sized_new(is->size);
- } else {
- bdatas = g_byte_array_sized_new(MODPLUG_PREALLOC_BLOCK);
- }
-
- data = g_malloc(MODPLUG_READ_BLOCK);
-
- while (true) {
- ret = decoder_read(decoder, is, data, MODPLUG_READ_BLOCK);
- if (ret == 0) {
- if (input_stream_lock_eof(is))
- /* end of file */
- break;
-
- /* I/O error - skip this song */
- g_free(data);
- g_byte_array_free(bdatas, true);
- return NULL;
- }
-
- if (bdatas->len + ret > MODPLUG_FILE_LIMIT) {
- g_warning("stream too large\n");
- g_free(data);
- g_byte_array_free(bdatas, TRUE);
- return NULL;
- }
-
- g_byte_array_append(bdatas, data, ret);
- }
-
- g_free(data);
-
- return bdatas;
-}
-
-static void
-mod_decode(struct decoder *decoder, struct input_stream *is)
-{
- ModPlugFile *f;
- ModPlug_Settings settings;
- GByteArray *bdatas;
- struct audio_format audio_format;
- int ret;
- char audio_buffer[MODPLUG_FRAME_SIZE];
- enum decoder_command cmd = DECODE_COMMAND_NONE;
-
- bdatas = mod_loadfile(decoder, is);
-
- if (!bdatas) {
- g_warning("could not load stream\n");
- return;
- }
-
- ModPlug_GetSettings(&settings);
- /* alter setting */
- settings.mResamplingMode = MODPLUG_RESAMPLE_FIR; /* RESAMP */
- settings.mChannels = 2;
- settings.mBits = 16;
- settings.mFrequency = 44100;
- /* insert more setting changes here */
- ModPlug_SetSettings(&settings);
-
- f = ModPlug_Load(bdatas->data, bdatas->len);
- g_byte_array_free(bdatas, TRUE);
- if (!f) {
- g_warning("could not decode stream\n");
- return;
- }
-
- audio_format_init(&audio_format, 44100, SAMPLE_FORMAT_S16, 2);
- assert(audio_format_valid(&audio_format));
-
- decoder_initialized(decoder, &audio_format,
- is->seekable, ModPlug_GetLength(f) / 1000.0);
-
- do {
- ret = ModPlug_Read(f, audio_buffer, MODPLUG_FRAME_SIZE);
- if (ret <= 0)
- break;
-
- cmd = decoder_data(decoder, NULL,
- audio_buffer, ret,
- 0);
-
- if (cmd == DECODE_COMMAND_SEEK) {
- float where = decoder_seek_where(decoder);
-
- ModPlug_Seek(f, (int)(where * 1000.0));
-
- decoder_command_finished(decoder);
- }
-
- } while (cmd != DECODE_COMMAND_STOP);
-
- ModPlug_Unload(f);
-}
-
-static bool
-modplug_scan_stream(struct input_stream *is,
- const struct tag_handler *handler, void *handler_ctx)
-{
- ModPlugFile *f;
- GByteArray *bdatas;
-
- bdatas = mod_loadfile(NULL, is);
- if (!bdatas)
- return false;
-
- f = ModPlug_Load(bdatas->data, bdatas->len);
- g_byte_array_free(bdatas, TRUE);
- if (f == NULL)
- return false;
-
- tag_handler_invoke_duration(handler, handler_ctx,
- ModPlug_GetLength(f) / 1000);
-
- const char *title = ModPlug_GetName(f);
- if (title != NULL)
- tag_handler_invoke_tag(handler, handler_ctx,
- TAG_TITLE, title);
-
- ModPlug_Unload(f);
-
- return true;
-}
-
-static const char *const mod_suffixes[] = {
- "669", "amf", "ams", "dbm", "dfm", "dsm", "far", "it",
- "med", "mdl", "mod", "mtm", "mt2", "okt", "s3m", "stm",
- "ult", "umx", "xm",
- NULL
-};
-
-const struct decoder_plugin modplug_decoder_plugin = {
- .name = "modplug",
- .stream_decode = mod_decode,
- .scan_stream = modplug_scan_stream,
- .suffixes = mod_suffixes,
-};
diff --git a/src/decoder/mp4ff_decoder_plugin.c b/src/decoder/mp4ff_decoder_plugin.c
deleted file mode 100644
index ca78a22d0..000000000
--- a/src/decoder/mp4ff_decoder_plugin.c
+++ /dev/null
@@ -1,448 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "decoder_api.h"
-#include "audio_check.h"
-#include "tag_table.h"
-#include "tag_handler.h"
-
-#include <glib.h>
-
-#include <mp4ff.h>
-#include <faad.h>
-
-#include <assert.h>
-#include <stdlib.h>
-#include <unistd.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "mp4ff"
-
-/* all code here is either based on or copied from FAAD2's frontend code */
-
-struct mp4ff_input_stream {
- mp4ff_callback_t callback;
-
- struct decoder *decoder;
- struct input_stream *input_stream;
-};
-
-static int
-mp4_get_aac_track(mp4ff_t * infile, faacDecHandle decoder,
- uint32_t *sample_rate, unsigned char *channels_r)
-{
-#ifdef HAVE_FAAD_LONG
- /* neaacdec.h declares all arguments as "unsigned long", but
- internally expects uint32_t pointers. To avoid gcc
- warnings, use this workaround. */
- unsigned long *sample_rate_r = (unsigned long*)sample_rate;
-#else
- uint32_t *sample_rate_r = sample_rate;
-#endif
- int i, rc;
- int num_tracks = mp4ff_total_tracks(infile);
-
- for (i = 0; i < num_tracks; i++) {
- unsigned char *buff = NULL;
- unsigned int buff_size = 0;
-
- if (mp4ff_get_track_type(infile, i) != 1)
- /* not an audio track */
- continue;
-
- if (decoder == NULL)
- /* have don't have a decoder to initialize -
- we're done now, because we found an audio
- track */
- return i;
-
- mp4ff_get_decoder_config(infile, i, &buff, &buff_size);
- if (buff == NULL)
- continue;
-
- rc = faacDecInit2(decoder, buff, buff_size,
- sample_rate_r, channels_r);
- free(buff);
-
- if (rc >= 0)
- /* found a valid AAC track */
- return i;
- }
-
- /* can't decode this */
- return -1;
-}
-
-static uint32_t
-mp4_read(void *user_data, void *buffer, uint32_t length)
-{
- struct mp4ff_input_stream *mis = user_data;
-
- if (length == 0)
- /* libmp4ff is known to attempt to read 0 bytes - make
- this a special case, because the input_stream API
- would not allow this */
- return 0;
-
- return decoder_read(mis->decoder, mis->input_stream, buffer, length);
-}
-
-static uint32_t
-mp4_seek(void *user_data, uint64_t position)
-{
- struct mp4ff_input_stream *mis = user_data;
-
- return input_stream_lock_seek(mis->input_stream, position, SEEK_SET,
- NULL)
- ? 0 : -1;
-}
-
-static const mp4ff_callback_t mpd_mp4ff_callback = {
- .read = mp4_read,
- .seek = mp4_seek,
-};
-
-static mp4ff_t *
-mp4ff_input_stream_open(struct mp4ff_input_stream *mis,
- struct decoder *decoder,
- struct input_stream *input_stream)
-{
- mis->callback = mpd_mp4ff_callback;
- mis->callback.user_data = mis;
- mis->decoder = decoder;
- mis->input_stream = input_stream;
-
- return mp4ff_open_read(&mis->callback);
-}
-
-static faacDecHandle
-mp4_faad_new(mp4ff_t *mp4fh, int *track_r, struct audio_format *audio_format)
-{
- faacDecHandle decoder;
- faacDecConfigurationPtr config;
- int track;
- uint32_t sample_rate;
- unsigned char channels;
- GError *error = NULL;
-
- decoder = faacDecOpen();
-
- config = faacDecGetCurrentConfiguration(decoder);
- config->outputFormat = FAAD_FMT_16BIT;
-#ifdef HAVE_FAACDECCONFIGURATION_DOWNMATRIX
- config->downMatrix = 1;
-#endif
-#ifdef HAVE_FAACDECCONFIGURATION_DONTUPSAMPLEIMPLICITSBR
- config->dontUpSampleImplicitSBR = 0;
-#endif
- faacDecSetConfiguration(decoder, config);
-
- track = mp4_get_aac_track(mp4fh, decoder, &sample_rate, &channels);
- if (track < 0) {
- g_warning("No AAC track found");
- faacDecClose(decoder);
- return NULL;
- }
-
- if (!audio_format_init_checked(audio_format, sample_rate,
- SAMPLE_FORMAT_S16, channels,
- &error)) {
- g_warning("%s", error->message);
- g_error_free(error);
- faacDecClose(decoder);
- return NULL;
- }
-
- *track_r = track;
-
- return decoder;
-}
-
-static void
-mp4_decode(struct decoder *mpd_decoder, struct input_stream *input_stream)
-{
- struct mp4ff_input_stream mis;
- mp4ff_t *mp4fh;
- int32_t track;
- float file_time, total_time;
- int32_t scale;
- faacDecHandle decoder;
- struct audio_format audio_format;
- faacDecFrameInfo frame_info;
- unsigned char *mp4_buffer;
- unsigned int mp4_buffer_size;
- long sample_id;
- long num_samples;
- long dur;
- unsigned int sample_count;
- char *sample_buffer;
- size_t sample_buffer_length;
- unsigned int initial = 1;
- float *seek_table;
- long seek_table_end = -1;
- bool seek_position_found = false;
- long offset;
- uint16_t bit_rate = 0;
- bool seeking = false;
- double seek_where = 0;
- enum decoder_command cmd = DECODE_COMMAND_NONE;
-
- mp4fh = mp4ff_input_stream_open(&mis, mpd_decoder, input_stream);
- if (!mp4fh) {
- g_warning("Input does not appear to be a mp4 stream.\n");
- return;
- }
-
- decoder = mp4_faad_new(mp4fh, &track, &audio_format);
- if (decoder == NULL) {
- mp4ff_close(mp4fh);
- return;
- }
-
- file_time = mp4ff_get_track_duration_use_offsets(mp4fh, track);
- scale = mp4ff_time_scale(mp4fh, track);
-
- if (scale < 0) {
- g_warning("Error getting audio format of mp4 AAC track.\n");
- faacDecClose(decoder);
- mp4ff_close(mp4fh);
- return;
- }
- total_time = ((float)file_time) / scale;
-
- num_samples = mp4ff_num_samples(mp4fh, track);
- if (num_samples > (long)(G_MAXINT / sizeof(float))) {
- g_warning("Integer overflow.\n");
- faacDecClose(decoder);
- mp4ff_close(mp4fh);
- return;
- }
-
- file_time = 0.0;
-
- seek_table = input_stream->seekable
- ? g_malloc(sizeof(float) * num_samples)
- : NULL;
-
- decoder_initialized(mpd_decoder, &audio_format,
- input_stream->seekable,
- total_time);
-
- for (sample_id = 0;
- sample_id < num_samples && cmd != DECODE_COMMAND_STOP;
- sample_id++) {
- if (cmd == DECODE_COMMAND_SEEK) {
- assert(seek_table != NULL);
-
- seeking = true;
- seek_where = decoder_seek_where(mpd_decoder);
- }
-
- if (seeking && seek_table_end > 1 &&
- seek_table[seek_table_end] >= seek_where) {
- int i = 2;
-
- assert(seek_table != NULL);
-
- while (seek_table[i] < seek_where)
- i++;
- sample_id = i - 1;
- file_time = seek_table[sample_id];
- }
-
- dur = mp4ff_get_sample_duration(mp4fh, track, sample_id);
- offset = mp4ff_get_sample_offset(mp4fh, track, sample_id);
-
- if (seek_table != NULL && sample_id > seek_table_end) {
- seek_table[sample_id] = file_time;
- seek_table_end = sample_id;
- }
-
- if (sample_id == 0)
- dur = 0;
- if (offset > dur)
- dur = 0;
- else
- dur -= offset;
- file_time += ((float)dur) / scale;
-
- if (seeking && file_time >= seek_where)
- seek_position_found = true;
-
- if (seeking && seek_position_found) {
- seek_position_found = false;
- seeking = 0;
- decoder_command_finished(mpd_decoder);
- }
-
- if (seeking)
- continue;
-
- if (mp4ff_read_sample(mp4fh, track, sample_id, &mp4_buffer,
- &mp4_buffer_size) == 0)
- break;
-
-#ifdef HAVE_FAAD_BUFLEN_FUNCS
- sample_buffer = faacDecDecode(decoder, &frame_info, mp4_buffer,
- mp4_buffer_size);
-#else
- sample_buffer = faacDecDecode(decoder, &frame_info, mp4_buffer);
-#endif
-
- free(mp4_buffer);
-
- if (frame_info.error > 0) {
- g_warning("faad2 error: %s\n",
- faacDecGetErrorMessage(frame_info.error));
- break;
- }
-
- if (frame_info.channels != audio_format.channels) {
- g_warning("channel count changed from %u to %u",
- audio_format.channels, frame_info.channels);
- break;
- }
-
-#ifdef HAVE_FAACDECFRAMEINFO_SAMPLERATE
- if (frame_info.samplerate != audio_format.sample_rate) {
- g_warning("sample rate changed from %u to %lu",
- audio_format.sample_rate,
- (unsigned long)frame_info.samplerate);
- break;
- }
-#endif
-
- if (audio_format.channels * (unsigned long)(dur + offset) > frame_info.samples) {
- dur = frame_info.samples / audio_format.channels;
- offset = 0;
- }
-
- sample_count = (unsigned long)(dur * audio_format.channels);
-
- if (sample_count > 0) {
- initial = 0;
- bit_rate = frame_info.bytesconsumed * 8.0 *
- frame_info.channels * scale /
- frame_info.samples / 1000 + 0.5;
- }
-
- sample_buffer_length = sample_count * 2;
-
- sample_buffer += offset * audio_format.channels * 2;
-
- cmd = decoder_data(mpd_decoder, input_stream,
- sample_buffer, sample_buffer_length,
- bit_rate);
- }
-
- g_free(seek_table);
- faacDecClose(decoder);
- mp4ff_close(mp4fh);
-}
-
-static const struct tag_table mp4ff_tags[] = {
- { "album artist", TAG_ALBUM_ARTIST },
- { "writer", TAG_COMPOSER },
- { "band", TAG_PERFORMER },
- { NULL, TAG_NUM_OF_ITEM_TYPES }
-};
-
-static enum tag_type
-mp4ff_tag_name_parse(const char *name)
-{
- enum tag_type type = tag_table_lookup_i(mp4ff_tags, name);
- if (type == TAG_NUM_OF_ITEM_TYPES)
- type = tag_name_parse_i(name);
-
- if (g_ascii_strcasecmp(name, "albumartist") == 0 ||
- g_ascii_strcasecmp(name, "album_artist") == 0)
- return TAG_ALBUM_ARTIST;
-
- return type;
-}
-
-static bool
-mp4ff_scan_stream(struct input_stream *is,
- const struct tag_handler *handler, void *handler_ctx)
-{
- struct mp4ff_input_stream mis;
- int32_t track;
- int32_t file_time;
- int32_t scale;
- int i;
-
- mp4ff_t *mp4fh = mp4ff_input_stream_open(&mis, NULL, is);
- if (mp4fh == NULL)
- return false;
-
- track = mp4_get_aac_track(mp4fh, NULL, NULL, NULL);
- if (track < 0) {
- mp4ff_close(mp4fh);
- return false;
- }
-
- file_time = mp4ff_get_track_duration_use_offsets(mp4fh, track);
- scale = mp4ff_time_scale(mp4fh, track);
- if (scale < 0) {
- mp4ff_close(mp4fh);
- return false;
- }
-
- tag_handler_invoke_duration(handler, handler_ctx,
- ((float)file_time) / scale + 0.5);
-
- for (i = 0; i < mp4ff_meta_get_num_items(mp4fh); i++) {
- char *item;
- char *value;
-
- mp4ff_meta_get_by_index(mp4fh, i, &item, &value);
-
- tag_handler_invoke_pair(handler, handler_ctx, item, value);
-
- enum tag_type type = mp4ff_tag_name_parse(item);
- if (type != TAG_NUM_OF_ITEM_TYPES)
- tag_handler_invoke_tag(handler, handler_ctx,
- type, value);
-
- free(item);
- free(value);
- }
-
- mp4ff_close(mp4fh);
-
- return true;
-}
-
-static const char *const mp4_suffixes[] = {
- "m4a",
- "m4b",
- "mp4",
- NULL
-};
-
-static const char *const mp4_mime_types[] = { "audio/mp4", "audio/m4a", NULL };
-
-const struct decoder_plugin mp4ff_decoder_plugin = {
- .name = "mp4ff",
- .stream_decode = mp4_decode,
- .scan_stream = mp4ff_scan_stream,
- .suffixes = mp4_suffixes,
- .mime_types = mp4_mime_types,
-};
diff --git a/src/decoder/mpcdec_decoder_plugin.c b/src/decoder/mpcdec_decoder_plugin.c
deleted file mode 100644
index d4768b35b..000000000
--- a/src/decoder/mpcdec_decoder_plugin.c
+++ /dev/null
@@ -1,347 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "decoder_api.h"
-#include "audio_check.h"
-#include "tag_handler.h"
-
-#ifdef MPC_IS_OLD_API
-#include <mpcdec/mpcdec.h>
-#else
-#include <mpc/mpcdec.h>
-#include <math.h>
-#endif
-
-#include <glib.h>
-#include <assert.h>
-#include <unistd.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "mpcdec"
-
-struct mpc_decoder_data {
- struct input_stream *is;
- struct decoder *decoder;
-};
-
-#ifdef MPC_IS_OLD_API
-#define cb_first_arg void *vdata
-#define cb_data vdata
-#else
-#define cb_first_arg mpc_reader *reader
-#define cb_data reader->data
-#endif
-
-static mpc_int32_t
-mpc_read_cb(cb_first_arg, void *ptr, mpc_int32_t size)
-{
- struct mpc_decoder_data *data = (struct mpc_decoder_data *) cb_data;
-
- return decoder_read(data->decoder, data->is, ptr, size);
-}
-
-static mpc_bool_t
-mpc_seek_cb(cb_first_arg, mpc_int32_t offset)
-{
- struct mpc_decoder_data *data = (struct mpc_decoder_data *) cb_data;
-
- return input_stream_lock_seek(data->is, offset, SEEK_SET, NULL);
-}
-
-static mpc_int32_t
-mpc_tell_cb(cb_first_arg)
-{
- struct mpc_decoder_data *data = (struct mpc_decoder_data *) cb_data;
-
- return (long)(data->is->offset);
-}
-
-static mpc_bool_t
-mpc_canseek_cb(cb_first_arg)
-{
- struct mpc_decoder_data *data = (struct mpc_decoder_data *) cb_data;
-
- return data->is->seekable;
-}
-
-static mpc_int32_t
-mpc_getsize_cb(cb_first_arg)
-{
- struct mpc_decoder_data *data = (struct mpc_decoder_data *) cb_data;
-
- return data->is->size;
-}
-
-/* this _looks_ performance-critical, don't de-inline -- eric */
-static inline int32_t
-mpc_to_mpd_sample(MPC_SAMPLE_FORMAT sample)
-{
- /* only doing 16-bit audio for now */
- int32_t val;
-
- enum {
- bits = 24,
- };
-
- const int clip_min = -1 << (bits - 1);
- const int clip_max = (1 << (bits - 1)) - 1;
-
-#ifdef MPC_FIXED_POINT
- const int shift = bits - MPC_FIXED_POINT_SCALE_SHIFT;
-
- if (shift < 0)
- val = sample >> -shift;
- else
- val = sample << shift;
-#else
- const int float_scale = 1 << (bits - 1);
-
- val = sample * float_scale;
-#endif
-
- if (val < clip_min)
- val = clip_min;
- else if (val > clip_max)
- val = clip_max;
-
- return val;
-}
-
-static void
-mpc_to_mpd_buffer(int32_t *dest, const MPC_SAMPLE_FORMAT *src,
- unsigned num_samples)
-{
- while (num_samples-- > 0)
- *dest++ = mpc_to_mpd_sample(*src++);
-}
-
-static void
-mpcdec_decode(struct decoder *mpd_decoder, struct input_stream *is)
-{
-#ifdef MPC_IS_OLD_API
- mpc_decoder decoder;
-#else
- mpc_demux *demux;
- mpc_frame_info frame;
- mpc_status status;
-#endif
- mpc_reader reader;
- mpc_streaminfo info;
- GError *error = NULL;
- struct audio_format audio_format;
-
- struct mpc_decoder_data data;
-
- MPC_SAMPLE_FORMAT sample_buffer[MPC_DECODER_BUFFER_LENGTH];
-
- mpc_uint32_t ret;
- int32_t chunk[G_N_ELEMENTS(sample_buffer)];
- long bit_rate = 0;
- mpc_uint32_t vbr_update_bits;
- enum decoder_command cmd = DECODE_COMMAND_NONE;
-
- data.is = is;
- data.decoder = mpd_decoder;
-
- reader.read = mpc_read_cb;
- reader.seek = mpc_seek_cb;
- reader.tell = mpc_tell_cb;
- reader.get_size = mpc_getsize_cb;
- reader.canseek = mpc_canseek_cb;
- reader.data = &data;
-
-#ifdef MPC_IS_OLD_API
- mpc_streaminfo_init(&info);
-
- if ((ret = mpc_streaminfo_read(&info, &reader)) != ERROR_CODE_OK) {
- if (decoder_get_command(mpd_decoder) != DECODE_COMMAND_STOP)
- g_warning("Not a valid musepack stream\n");
- return;
- }
-
- mpc_decoder_setup(&decoder, &reader);
-
- if (!mpc_decoder_initialize(&decoder, &info)) {
- if (decoder_get_command(mpd_decoder) != DECODE_COMMAND_STOP)
- g_warning("Not a valid musepack stream\n");
- return;
- }
-#else
- demux = mpc_demux_init(&reader);
- if (demux == NULL) {
- if (decoder_get_command(mpd_decoder) != DECODE_COMMAND_STOP)
- g_warning("Not a valid musepack stream");
- return;
- }
-
- mpc_demux_get_info(demux, &info);
-#endif
-
- if (!audio_format_init_checked(&audio_format, info.sample_freq,
- SAMPLE_FORMAT_S24_P32,
- info.channels, &error)) {
- g_warning("%s", error->message);
- g_error_free(error);
-#ifndef MPC_IS_OLD_API
- mpc_demux_exit(demux);
-#endif
- return;
- }
-
- struct replay_gain_info replay_gain_info;
- replay_gain_info_init(&replay_gain_info);
-#ifdef MPC_IS_OLD_API
- replay_gain_info.tuples[REPLAY_GAIN_ALBUM].gain = info.gain_album * 0.01;
- replay_gain_info.tuples[REPLAY_GAIN_ALBUM].peak = info.peak_album / 32767.0;
- replay_gain_info.tuples[REPLAY_GAIN_TRACK].gain = info.gain_title * 0.01;
- replay_gain_info.tuples[REPLAY_GAIN_TRACK].peak = info.peak_title / 32767.0;
-#else
- replay_gain_info.tuples[REPLAY_GAIN_ALBUM].gain = MPC_OLD_GAIN_REF - (info.gain_album / 256.);
- replay_gain_info.tuples[REPLAY_GAIN_ALBUM].peak = pow(10, info.peak_album / 256. / 20) / 32767;
- replay_gain_info.tuples[REPLAY_GAIN_TRACK].gain = MPC_OLD_GAIN_REF - (info.gain_title / 256.);
- replay_gain_info.tuples[REPLAY_GAIN_TRACK].peak = pow(10, info.peak_title / 256. / 20) / 32767;
-#endif
-
- decoder_replay_gain(mpd_decoder, &replay_gain_info);
-
- decoder_initialized(mpd_decoder, &audio_format,
- is->seekable,
- mpc_streaminfo_get_length(&info));
-
- do {
- if (cmd == DECODE_COMMAND_SEEK) {
- mpc_int64_t where = decoder_seek_where(mpd_decoder) *
- audio_format.sample_rate;
- bool success;
-
-#ifdef MPC_IS_OLD_API
- success = mpc_decoder_seek_sample(&decoder, where);
-#else
- success = mpc_demux_seek_sample(demux, where)
- == MPC_STATUS_OK;
-#endif
- if (success)
- decoder_command_finished(mpd_decoder);
- else
- decoder_seek_error(mpd_decoder);
- }
-
- vbr_update_bits = 0;
-
-#ifdef MPC_IS_OLD_API
- mpc_uint32_t vbr_update_acc = 0;
-
- ret = mpc_decoder_decode(&decoder, sample_buffer,
- &vbr_update_acc, &vbr_update_bits);
- if (ret == 0 || ret == (mpc_uint32_t)-1)
- break;
-#else
- frame.buffer = (MPC_SAMPLE_FORMAT *)sample_buffer;
- status = mpc_demux_decode(demux, &frame);
- if (status != MPC_STATUS_OK) {
- g_warning("Failed to decode sample");
- break;
- }
-
- if (frame.bits == -1)
- break;
-
- ret = frame.samples;
-#endif
-
- ret *= info.channels;
-
- mpc_to_mpd_buffer(chunk, sample_buffer, ret);
-
- bit_rate = vbr_update_bits * audio_format.sample_rate
- / 1152 / 1000;
-
- cmd = decoder_data(mpd_decoder, is,
- chunk, ret * sizeof(chunk[0]),
- bit_rate);
- } while (cmd != DECODE_COMMAND_STOP);
-
-#ifndef MPC_IS_OLD_API
- mpc_demux_exit(demux);
-#endif
-}
-
-static float
-mpcdec_get_file_duration(struct input_stream *is)
-{
- float total_time = -1;
-
- mpc_reader reader;
-#ifndef MPC_IS_OLD_API
- mpc_demux *demux;
-#endif
- mpc_streaminfo info;
- struct mpc_decoder_data data;
-
- data.is = is;
- data.decoder = NULL;
-
- reader.read = mpc_read_cb;
- reader.seek = mpc_seek_cb;
- reader.tell = mpc_tell_cb;
- reader.get_size = mpc_getsize_cb;
- reader.canseek = mpc_canseek_cb;
- reader.data = &data;
-
-#ifdef MPC_IS_OLD_API
- mpc_streaminfo_init(&info);
-
- if (mpc_streaminfo_read(&info, &reader) != ERROR_CODE_OK)
- return -1;
-#else
- demux = mpc_demux_init(&reader);
- if (demux == NULL)
- return -1;
-
- mpc_demux_get_info(demux, &info);
- mpc_demux_exit(demux);
-#endif
-
- total_time = mpc_streaminfo_get_length(&info);
-
- return total_time;
-}
-
-static bool
-mpcdec_scan_stream(struct input_stream *is,
- const struct tag_handler *handler, void *handler_ctx)
-{
- float total_time = mpcdec_get_file_duration(is);
-
- if (total_time < 0)
- return false;
-
- tag_handler_invoke_duration(handler, handler_ctx, total_time);
- return true;
-}
-
-static const char *const mpcdec_suffixes[] = { "mpc", NULL };
-
-const struct decoder_plugin mpcdec_decoder_plugin = {
- .name = "mpcdec",
- .stream_decode = mpcdec_decode,
- .scan_stream = mpcdec_scan_stream,
- .suffixes = mpcdec_suffixes,
-};
diff --git a/src/decoder/mpg123_decoder_plugin.c b/src/decoder/mpg123_decoder_plugin.c
deleted file mode 100644
index 657a9c889..000000000
--- a/src/decoder/mpg123_decoder_plugin.c
+++ /dev/null
@@ -1,245 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h" /* must be first for large file support */
-#include "decoder_api.h"
-#include "audio_check.h"
-#include "tag_handler.h"
-
-#include <glib.h>
-
-#include <mpg123.h>
-#include <stdio.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "mpg123"
-
-static bool
-mpd_mpg123_init(G_GNUC_UNUSED const struct config_param *param)
-{
- mpg123_init();
-
- return true;
-}
-
-static void
-mpd_mpg123_finish(void)
-{
- mpg123_exit();
-}
-
-/**
- * Opens a file with an existing #mpg123_handle.
- *
- * @param handle a handle which was created before; on error, this
- * function will not free it
- * @param audio_format this parameter is filled after successful
- * return
- * @return true on success
- */
-static bool
-mpd_mpg123_open(mpg123_handle *handle, const char *path_fs,
- struct audio_format *audio_format)
-{
- GError *gerror = NULL;
- char *path_dup;
- int error;
- int channels, encoding;
- long rate;
-
- /* mpg123_open() wants a writable string :-( */
- path_dup = g_strdup(path_fs);
-
- error = mpg123_open(handle, path_dup);
- g_free(path_dup);
- if (error != MPG123_OK) {
- g_warning("libmpg123 failed to open %s: %s",
- path_fs, mpg123_plain_strerror(error));
- return false;
- }
-
- /* obtain the audio format */
-
- error = mpg123_getformat(handle, &rate, &channels, &encoding);
- if (error != MPG123_OK) {
- g_warning("mpg123_getformat() failed: %s",
- mpg123_plain_strerror(error));
- return false;
- }
-
- if (encoding != MPG123_ENC_SIGNED_16) {
- /* other formats not yet implemented */
- g_warning("expected MPG123_ENC_SIGNED_16, got %d", encoding);
- return false;
- }
-
- if (!audio_format_init_checked(audio_format, rate, SAMPLE_FORMAT_S16,
- channels, &gerror)) {
- g_warning("%s", gerror->message);
- g_error_free(gerror);
- return false;
- }
-
- return true;
-}
-
-static void
-mpd_mpg123_file_decode(struct decoder *decoder, const char *path_fs)
-{
- struct audio_format audio_format;
- mpg123_handle *handle;
- int error;
- off_t num_samples;
- enum decoder_command cmd;
- struct mpg123_frameinfo info;
-
- /* open the file */
-
- handle = mpg123_new(NULL, &error);
- if (handle == NULL) {
- g_warning("mpg123_new() failed: %s",
- mpg123_plain_strerror(error));
- return;
- }
-
- if (!mpd_mpg123_open(handle, path_fs, &audio_format)) {
- mpg123_delete(handle);
- return;
- }
-
- num_samples = mpg123_length(handle);
-
- /* tell MPD core we're ready */
-
- decoder_initialized(decoder, &audio_format, true,
- (float)num_samples /
- (float)audio_format.sample_rate);
-
- if (mpg123_info(handle, &info) != MPG123_OK) {
- info.vbr = MPG123_CBR;
- info.bitrate = 0;
- }
-
- switch (info.vbr) {
- case MPG123_ABR:
- info.bitrate = info.abr_rate;
- break;
- case MPG123_CBR:
- break;
- default:
- info.bitrate = 0;
- }
-
- /* the decoder main loop */
-
- do {
- unsigned char buffer[8192];
- size_t nbytes;
-
- /* decode */
-
- error = mpg123_read(handle, buffer, sizeof(buffer), &nbytes);
- if (error != MPG123_OK) {
- if (error != MPG123_DONE)
- g_warning("mpg123_read() failed: %s",
- mpg123_plain_strerror(error));
- break;
- }
-
- /* update bitrate for ABR/VBR */
- if (info.vbr != MPG123_CBR) {
- /* FIXME: maybe skip, as too expensive? */
- /* FIXME: maybe, (info.vbr == MPG123_VBR) ? */
- if (mpg123_info (handle, &info) != MPG123_OK)
- info.bitrate = 0;
- }
-
- /* send to MPD */
-
- cmd = decoder_data(decoder, NULL, buffer, nbytes, info.bitrate);
-
- if (cmd == DECODE_COMMAND_SEEK) {
- off_t c = decoder_seek_where(decoder)*audio_format.sample_rate;
- c = mpg123_seek(handle, c, SEEK_SET);
- if (c < 0)
- decoder_seek_error(decoder);
- else {
- decoder_command_finished(decoder);
- decoder_timestamp(decoder, c/(double)audio_format.sample_rate);
- }
-
- cmd = DECODE_COMMAND_NONE;
- }
- } while (cmd == DECODE_COMMAND_NONE);
-
- /* cleanup */
-
- mpg123_delete(handle);
-}
-
-static bool
-mpd_mpg123_scan_file(const char *path_fs,
- const struct tag_handler *handler, void *handler_ctx)
-{
- struct audio_format audio_format;
- mpg123_handle *handle;
- int error;
- off_t num_samples;
-
- handle = mpg123_new(NULL, &error);
- if (handle == NULL) {
- g_warning("mpg123_new() failed: %s",
- mpg123_plain_strerror(error));
- return false;
- }
-
- if (!mpd_mpg123_open(handle, path_fs, &audio_format)) {
- mpg123_delete(handle);
- return false;
- }
-
- num_samples = mpg123_length(handle);
- if (num_samples <= 0) {
- mpg123_delete(handle);
- return false;
- }
-
- /* ID3 tag support not yet implemented */
-
- mpg123_delete(handle);
-
- tag_handler_invoke_duration(handler, handler_ctx,
- num_samples / audio_format.sample_rate);
- return true;
-}
-
-static const char *const mpg123_suffixes[] = {
- "mp3",
- NULL
-};
-
-const struct decoder_plugin mpg123_decoder_plugin = {
- .name = "mpg123",
- .init = mpd_mpg123_init,
- .finish = mpd_mpg123_finish,
- .file_decode = mpd_mpg123_file_decode,
- /* streaming not yet implemented */
- .scan_file = mpd_mpg123_scan_file,
- .suffixes = mpg123_suffixes,
-};
diff --git a/src/decoder/pcm_decoder_plugin.c b/src/decoder/pcm_decoder_plugin.c
deleted file mode 100644
index fc7dffc05..000000000
--- a/src/decoder/pcm_decoder_plugin.c
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "decoder/pcm_decoder_plugin.h"
-#include "decoder_api.h"
-#include "util/byte_reverse.h"
-
-#include <glib.h>
-#include <unistd.h>
-#include <stdio.h> /* for SEEK_SET */
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "pcm"
-
-static void
-pcm_stream_decode(struct decoder *decoder, struct input_stream *is)
-{
- static const struct audio_format audio_format = {
- .sample_rate = 44100,
- .format = SAMPLE_FORMAT_S16,
- .channels = 2,
- };
-
- const bool reverse_endian = is->mime != NULL &&
- strcmp(is->mime, "audio/x-mpd-cdda-pcm-reverse") == 0;
-
- GError *error = NULL;
- enum decoder_command cmd;
-
- double time_to_size = audio_format_time_to_size(&audio_format);
-
- float total_time = -1;
- if (is->size >= 0)
- total_time = is->size / time_to_size;
-
- decoder_initialized(decoder, &audio_format, is->seekable, total_time);
-
- do {
- char buffer[4096];
-
- size_t nbytes = decoder_read(decoder, is,
- buffer, sizeof(buffer));
-
- if (nbytes == 0 && input_stream_lock_eof(is))
- break;
-
- if (reverse_endian)
- /* make sure we deliver samples in host byte order */
- reverse_bytes_16((uint16_t *)buffer,
- (uint16_t *)buffer,
- (uint16_t *)(buffer + nbytes));
-
- cmd = nbytes > 0
- ? decoder_data(decoder, is,
- buffer, nbytes, 0)
- : decoder_get_command(decoder);
- if (cmd == DECODE_COMMAND_SEEK) {
- goffset offset = (goffset)(time_to_size *
- decoder_seek_where(decoder));
- if (input_stream_lock_seek(is, offset, SEEK_SET,
- &error)) {
- decoder_command_finished(decoder);
- } else {
- g_warning("seeking failed: %s", error->message);
- g_error_free(error);
- decoder_seek_error(decoder);
- }
-
- cmd = DECODE_COMMAND_NONE;
- }
- } while (cmd == DECODE_COMMAND_NONE);
-}
-
-static const char *const pcm_mime_types[] = {
- /* for streams obtained by the cdio_paranoia input plugin */
- "audio/x-mpd-cdda-pcm",
-
- /* same as above, but with reverse byte order */
- "audio/x-mpd-cdda-pcm-reverse",
-
- NULL
-};
-
-const struct decoder_plugin pcm_decoder_plugin = {
- .name = "pcm",
- .stream_decode = pcm_stream_decode,
- .mime_types = pcm_mime_types,
-};
diff --git a/src/decoder/pcm_decoder_plugin.h b/src/decoder/pcm_decoder_plugin.h
deleted file mode 100644
index 11df80155..000000000
--- a/src/decoder/pcm_decoder_plugin.h
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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.
- */
-
-/** \file
- *
- * Not really a decoder; this plugin forwards its input data "as-is".
- *
- * It was written only to support the "cdio_paranoia" input plugin,
- * which does not need a decoder.
- */
-
-#ifndef MPD_DECODER_PCM_H
-#define MPD_DECODER_PCM_H
-
-extern const struct decoder_plugin pcm_decoder_plugin;
-
-#endif
diff --git a/src/decoder/sidplay_decoder_plugin.cxx b/src/decoder/sidplay_decoder_plugin.cxx
index 5d162f179..4e26efecb 100644
--- a/src/decoder/sidplay_decoder_plugin.cxx
+++ b/src/decoder/sidplay_decoder_plugin.cxx
@@ -18,14 +18,15 @@
*/
#include "config.h"
+#include "../DecoderAPI.hxx"
extern "C" {
-#include "../decoder_api.h"
-#include "tag_handler.h"
+#include "TagHandler.hxx"
}
#include <errno.h>
#include <stdlib.h>
+#include <string.h>
#include <glib.h>
#include <sidplay/sidplay2.h>
@@ -82,29 +83,27 @@ sidplay_load_songlength_db(const char *path)
}
static bool
-sidplay_init(const struct config_param *param)
+sidplay_init(const config_param &param)
{
/* read the songlengths database file */
- songlength_file=config_get_block_string(param,
- "songlength_database", NULL);
+ songlength_file = param.GetBlockValue("songlength_database");
if (songlength_file != NULL)
songlength_database = sidplay_load_songlength_db(songlength_file);
- default_songlength=config_get_block_unsigned(param,
- "default_songlength", 0);
+ default_songlength = param.GetBlockValue("default_songlength", 0u);
- all_files_are_containers=config_get_block_bool(param,
- "all_files_are_containers", true);
+ all_files_are_containers =
+ param.GetBlockValue("all_files_are_containers", true);
path_with_subtune=g_pattern_spec_new(
"*/" SUBTUNE_PREFIX "???.sid");
- filter_setting=config_get_block_bool(param, "filter", true);
+ filter_setting = param.GetBlockValue("filter", true);
return true;
}
-void
+static void
sidplay_finish()
{
g_pattern_spec_free(path_with_subtune);
@@ -136,7 +135,7 @@ get_container_name(const char *path_fs)
* returns tune number from file.sid/tune_xxx.sid style path or 1 if
* no subtune is appended
*/
-static int
+static unsigned
get_song_num(const char *path_fs)
{
if(g_pattern_match(path_with_subtune,
@@ -172,7 +171,7 @@ get_song_length(const char *path_fs)
char md5sum[SIDTUNE_MD5_LENGTH+1];
tune.createMD5(md5sum);
- int song_num=get_song_num(path_fs);
+ const unsigned song_num = get_song_num(path_fs);
gsize num_items;
gchar **values=g_key_file_get_string_list(songlength_database,
@@ -284,11 +283,10 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs)
/* initialize the MPD decoder */
- struct audio_format audio_format;
- audio_format_init(&audio_format, 48000, SAMPLE_FORMAT_S16, channels);
- assert(audio_format_valid(&audio_format));
+ const AudioFormat audio_format(48000, SampleFormat::S16, channels);
+ assert(audio_format.IsValid());
- decoder_initialized(decoder, &audio_format, true, (float)song_len);
+ decoder_initialized(decoder, audio_format, true, (float)song_len);
/* .. and play */
@@ -330,7 +328,7 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs)
decoder_command_finished(decoder);
}
- if (song_len > 0 && player.time() >= song_len)
+ if (song_len > 0 && player.time() >= (unsigned)song_len)
break;
} while (cmd != DECODE_COMMAND_STOP);
diff --git a/src/decoder/sndfile_decoder_plugin.c b/src/decoder/sndfile_decoder_plugin.c
deleted file mode 100644
index 8dd98236f..000000000
--- a/src/decoder/sndfile_decoder_plugin.c
+++ /dev/null
@@ -1,255 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "decoder_api.h"
-#include "audio_check.h"
-#include "tag_handler.h"
-
-#include <sndfile.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "sndfile"
-
-static sf_count_t
-sndfile_vio_get_filelen(void *user_data)
-{
- const struct input_stream *is = user_data;
-
- return is->size;
-}
-
-static sf_count_t
-sndfile_vio_seek(sf_count_t offset, int whence, void *user_data)
-{
- struct input_stream *is = user_data;
- bool success;
-
- success = input_stream_lock_seek(is, offset, whence, NULL);
- if (!success)
- return -1;
-
- return is->offset;
-}
-
-static sf_count_t
-sndfile_vio_read(void *ptr, sf_count_t count, void *user_data)
-{
- struct input_stream *is = user_data;
- GError *error = NULL;
- size_t nbytes;
-
- nbytes = input_stream_lock_read(is, ptr, count, &error);
- if (nbytes == 0 && error != NULL) {
- g_warning("%s", error->message);
- g_error_free(error);
- return -1;
- }
-
- return nbytes;
-}
-
-static sf_count_t
-sndfile_vio_write(G_GNUC_UNUSED const void *ptr,
- G_GNUC_UNUSED sf_count_t count,
- G_GNUC_UNUSED void *user_data)
-{
- /* no writing! */
- return -1;
-}
-
-static sf_count_t
-sndfile_vio_tell(void *user_data)
-{
- const struct input_stream *is = user_data;
-
- return is->offset;
-}
-
-/**
- * This SF_VIRTUAL_IO implementation wraps MPD's #input_stream to a
- * libsndfile stream.
- */
-static SF_VIRTUAL_IO vio = {
- .get_filelen = sndfile_vio_get_filelen,
- .seek = sndfile_vio_seek,
- .read = sndfile_vio_read,
- .write = sndfile_vio_write,
- .tell = sndfile_vio_tell,
-};
-
-/**
- * Converts a frame number to a timestamp (in seconds).
- */
-static float
-frame_to_time(sf_count_t frame, const struct audio_format *audio_format)
-{
- return (float)frame / (float)audio_format->sample_rate;
-}
-
-/**
- * Converts a timestamp (in seconds) to a frame number.
- */
-static sf_count_t
-time_to_frame(float t, const struct audio_format *audio_format)
-{
- return (sf_count_t)(t * audio_format->sample_rate);
-}
-
-static void
-sndfile_stream_decode(struct decoder *decoder, struct input_stream *is)
-{
- GError *error = NULL;
- SNDFILE *sf;
- SF_INFO info;
- struct audio_format audio_format;
- size_t frame_size;
- sf_count_t read_frames, num_frames;
- int buffer[4096];
- enum decoder_command cmd;
-
- info.format = 0;
-
- sf = sf_open_virtual(&vio, SFM_READ, &info, is);
- if (sf == NULL) {
- g_warning("sf_open_virtual() failed");
- return;
- }
-
- /* for now, always read 32 bit samples. Later, we could lower
- MPD's CPU usage by reading 16 bit samples with
- sf_readf_short() on low-quality source files. */
- if (!audio_format_init_checked(&audio_format, info.samplerate,
- SAMPLE_FORMAT_S32,
- info.channels, &error)) {
- g_warning("%s", error->message);
- g_error_free(error);
- return;
- }
-
- decoder_initialized(decoder, &audio_format, info.seekable,
- frame_to_time(info.frames, &audio_format));
-
- frame_size = audio_format_frame_size(&audio_format);
- read_frames = sizeof(buffer) / frame_size;
-
- do {
- num_frames = sf_readf_int(sf, buffer, read_frames);
- if (num_frames <= 0)
- break;
-
- cmd = decoder_data(decoder, is,
- buffer, num_frames * frame_size,
- 0);
- if (cmd == DECODE_COMMAND_SEEK) {
- sf_count_t c =
- time_to_frame(decoder_seek_where(decoder),
- &audio_format);
- c = sf_seek(sf, c, SEEK_SET);
- if (c < 0)
- decoder_seek_error(decoder);
- else
- decoder_command_finished(decoder);
- cmd = DECODE_COMMAND_NONE;
- }
- } while (cmd == DECODE_COMMAND_NONE);
-
- sf_close(sf);
-}
-
-static bool
-sndfile_scan_file(const char *path_fs,
- const struct tag_handler *handler, void *handler_ctx)
-{
- SNDFILE *sf;
- SF_INFO info;
- const char *p;
-
- info.format = 0;
-
- sf = sf_open(path_fs, SFM_READ, &info);
- if (sf == NULL)
- return false;
-
- if (!audio_valid_sample_rate(info.samplerate)) {
- sf_close(sf);
- g_warning("Invalid sample rate in %s\n", path_fs);
- return false;
- }
-
- tag_handler_invoke_duration(handler, handler_ctx,
- info.frames / info.samplerate);
-
- p = sf_get_string(sf, SF_STR_TITLE);
- if (p != NULL)
- tag_handler_invoke_tag(handler, handler_ctx,
- TAG_TITLE, p);
-
- p = sf_get_string(sf, SF_STR_ARTIST);
- if (p != NULL)
- tag_handler_invoke_tag(handler, handler_ctx,
- TAG_ARTIST, p);
-
- p = sf_get_string(sf, SF_STR_DATE);
- if (p != NULL)
- tag_handler_invoke_tag(handler, handler_ctx,
- TAG_DATE, p);
-
- sf_close(sf);
-
- return true;
-}
-
-static const char *const sndfile_suffixes[] = {
- "wav", "aiff", "aif", /* Microsoft / SGI / Apple */
- "au", "snd", /* Sun / DEC / NeXT */
- "paf", /* Paris Audio File */
- "iff", "svx", /* Commodore Amiga IFF / SVX */
- "sf", /* IRCAM */
- "voc", /* Creative */
- "w64", /* Soundforge */
- "pvf", /* Portable Voice Format */
- "xi", /* Fasttracker */
- "htk", /* HMM Tool Kit */
- "caf", /* Apple */
- "sd2", /* Sound Designer II */
-
- /* libsndfile also supports FLAC and Ogg Vorbis, but only by
- linking with libFLAC and libvorbis - we can do better, we
- have native plugins for these libraries */
-
- NULL
-};
-
-static const char *const sndfile_mime_types[] = {
- "audio/x-wav",
- "audio/x-aiff",
-
- /* what are the MIME types of the other supported formats? */
-
- NULL
-};
-
-const struct decoder_plugin sndfile_decoder_plugin = {
- .name = "sndfile",
- .stream_decode = sndfile_stream_decode,
- .scan_file = sndfile_scan_file,
- .suffixes = sndfile_suffixes,
- .mime_types = sndfile_mime_types,
-};
diff --git a/src/decoder/vorbis_comments.c b/src/decoder/vorbis_comments.c
deleted file mode 100644
index 6c2d57b72..000000000
--- a/src/decoder/vorbis_comments.c
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * 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 "vorbis_comments.h"
-#include "tag.h"
-#include "tag_table.h"
-#include "tag_handler.h"
-#include "replay_gain_info.h"
-
-#include <glib.h>
-#include <assert.h>
-#include <stddef.h>
-#include <string.h>
-#include <stdlib.h>
-
-static const char *
-vorbis_comment_value(const char *comment, const char *needle)
-{
- size_t len = strlen(needle);
-
- if (g_ascii_strncasecmp(comment, needle, len) == 0 &&
- comment[len] == '=')
- return comment + len + 1;
-
- return NULL;
-}
-
-bool
-vorbis_comments_to_replay_gain(struct replay_gain_info *rgi, char **comments)
-{
- const char *temp;
- bool found = false;
-
- replay_gain_info_init(rgi);
-
- while (*comments) {
- if ((temp =
- vorbis_comment_value(*comments, "replaygain_track_gain"))) {
- rgi->tuples[REPLAY_GAIN_TRACK].gain = atof(temp);
- found = true;
- } else if ((temp = vorbis_comment_value(*comments,
- "replaygain_album_gain"))) {
- rgi->tuples[REPLAY_GAIN_ALBUM].gain = atof(temp);
- found = true;
- } else if ((temp = vorbis_comment_value(*comments,
- "replaygain_track_peak"))) {
- rgi->tuples[REPLAY_GAIN_TRACK].peak = atof(temp);
- found = true;
- } else if ((temp = vorbis_comment_value(*comments,
- "replaygain_album_peak"))) {
- rgi->tuples[REPLAY_GAIN_ALBUM].peak = atof(temp);
- found = true;
- }
-
- comments++;
- }
-
- return found;
-}
-
-/**
- * Check if the comment's name equals the passed name, and if so, copy
- * the comment value into the tag.
- */
-static bool
-vorbis_copy_comment(const char *comment,
- const char *name, enum tag_type tag_type,
- const struct tag_handler *handler, void *handler_ctx)
-{
- const char *value;
-
- value = vorbis_comment_value(comment, name);
- if (value != NULL) {
- tag_handler_invoke_tag(handler, handler_ctx, tag_type, value);
- return true;
- }
-
- return false;
-}
-
-static const struct tag_table vorbis_tags[] = {
- { "tracknumber", TAG_TRACK },
- { "discnumber", TAG_DISC },
- { "album artist", TAG_ALBUM_ARTIST },
- { NULL, TAG_NUM_OF_ITEM_TYPES }
-};
-
-static void
-vorbis_scan_comment(const char *comment,
- const struct tag_handler *handler, void *handler_ctx)
-{
- if (handler->pair != NULL) {
- char *name = g_strdup((const char*)comment);
- char *value = strchr(name, '=');
-
- if (value != NULL && value > name) {
- *value++ = 0;
- tag_handler_invoke_pair(handler, handler_ctx,
- name, value);
- }
-
- g_free(name);
- }
-
- for (const struct tag_table *i = vorbis_tags; i->name != NULL; ++i)
- if (vorbis_copy_comment(comment, i->name, i->type,
- handler, handler_ctx))
- return;
-
- for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
- if (vorbis_copy_comment(comment,
- tag_item_names[i], i,
- handler, handler_ctx))
- return;
-}
-
-void
-vorbis_comments_scan(char **comments,
- const struct tag_handler *handler, void *handler_ctx)
-{
- while (*comments)
- vorbis_scan_comment(*comments++,
- handler, handler_ctx);
-
-}
-
-struct tag *
-vorbis_comments_to_tag(char **comments)
-{
- struct tag *tag = tag_new();
- vorbis_comments_scan(comments, &add_tag_handler, tag);
-
- if (tag_is_empty(tag)) {
- tag_free(tag);
- tag = NULL;
- }
-
- return tag;
-}
diff --git a/src/decoder/vorbis_comments.h b/src/decoder/vorbis_comments.h
deleted file mode 100644
index c15096930..000000000
--- a/src/decoder/vorbis_comments.h
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef MPD_VORBIS_COMMENTS_H
-#define MPD_VORBIS_COMMENTS_H
-
-#include "check.h"
-
-#include <stdbool.h>
-
-struct replay_gain_info;
-struct tag_handler;
-
-bool
-vorbis_comments_to_replay_gain(struct replay_gain_info *rgi, char **comments);
-
-void
-vorbis_comments_scan(char **comments,
- const struct tag_handler *handler, void *handler_ctx);
-
-struct tag *
-vorbis_comments_to_tag(char **comments);
-
-#endif
diff --git a/src/decoder/vorbis_decoder_plugin.c b/src/decoder/vorbis_decoder_plugin.c
deleted file mode 100644
index 15cdc0ca9..000000000
--- a/src/decoder/vorbis_decoder_plugin.c
+++ /dev/null
@@ -1,314 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "vorbis_comments.h"
-#include "_ogg_common.h"
-#include "audio_check.h"
-#include "uri.h"
-#include "tag_handler.h"
-
-#ifndef HAVE_TREMOR
-#define OV_EXCLUDE_STATIC_CALLBACKS
-#include <vorbis/vorbisfile.h>
-#else
-#include <tremor/ivorbisfile.h>
-/* Macros to make Tremor's API look like libogg. Tremor always
- returns host-byte-order 16-bit signed data, and uses integer
- milliseconds where libogg uses double seconds.
-*/
-#define ov_read(VF, BUFFER, LENGTH, BIGENDIANP, WORD, SGNED, BITSTREAM) \
- ov_read(VF, BUFFER, LENGTH, BITSTREAM)
-#define ov_time_total(VF, I) ((double)ov_time_total(VF, I)/1000)
-#define ov_time_tell(VF) ((double)ov_time_tell(VF)/1000)
-#define ov_time_seek_page(VF, S) (ov_time_seek_page(VF, (S)*1000))
-#endif /* HAVE_TREMOR */
-
-#include <glib.h>
-
-#include <assert.h>
-#include <errno.h>
-#include <unistd.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "vorbis"
-#define OGG_CHUNK_SIZE 4096
-
-#if G_BYTE_ORDER == G_BIG_ENDIAN
-#define OGG_DECODE_USE_BIGENDIAN 1
-#else
-#define OGG_DECODE_USE_BIGENDIAN 0
-#endif
-
-struct vorbis_input_stream {
- struct decoder *decoder;
-
- struct input_stream *input_stream;
- bool seekable;
-};
-
-static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *data)
-{
- struct vorbis_input_stream *vis = data;
- size_t ret;
-
- ret = decoder_read(vis->decoder, vis->input_stream, ptr, size * nmemb);
-
- errno = 0;
-
- return ret / size;
-}
-
-static int ogg_seek_cb(void *data, ogg_int64_t offset, int whence)
-{
- struct vorbis_input_stream *vis = data;
-
- return vis->seekable &&
- (!vis->decoder || decoder_get_command(vis->decoder) != DECODE_COMMAND_STOP) &&
- input_stream_lock_seek(vis->input_stream, offset, whence, NULL)
- ? 0 : -1;
-}
-
-/* TODO: check Ogg libraries API and see if we can just not have this func */
-static int ogg_close_cb(G_GNUC_UNUSED void *data)
-{
- return 0;
-}
-
-static long ogg_tell_cb(void *data)
-{
- const struct vorbis_input_stream *vis = data;
-
- return (long)vis->input_stream->offset;
-}
-
-static const ov_callbacks vorbis_is_callbacks = {
- .read_func = ogg_read_cb,
- .seek_func = ogg_seek_cb,
- .close_func = ogg_close_cb,
- .tell_func = ogg_tell_cb,
-};
-
-static const char *
-vorbis_strerror(int code)
-{
- switch (code) {
- case OV_EREAD:
- return "read error";
-
- case OV_ENOTVORBIS:
- return "not vorbis stream";
-
- case OV_EVERSION:
- return "vorbis version mismatch";
-
- case OV_EBADHEADER:
- return "invalid vorbis header";
-
- case OV_EFAULT:
- return "internal logic error";
-
- default:
- return "unknown error";
- }
-}
-
-static bool
-vorbis_is_open(struct vorbis_input_stream *vis, OggVorbis_File *vf,
- struct decoder *decoder, struct input_stream *input_stream)
-{
- vis->decoder = decoder;
- vis->input_stream = input_stream;
- vis->seekable = input_stream->seekable &&
- (input_stream->uri == NULL ||
- !uri_has_scheme(input_stream->uri));
-
- int ret = ov_open_callbacks(vis, vf, NULL, 0, vorbis_is_callbacks);
- if (ret < 0) {
- if (decoder == NULL ||
- decoder_get_command(decoder) == DECODE_COMMAND_NONE)
- g_warning("Failed to open Ogg Vorbis stream: %s",
- vorbis_strerror(ret));
- return false;
- }
-
- return true;
-}
-
-static void
-vorbis_send_comments(struct decoder *decoder, struct input_stream *is,
- char **comments)
-{
- struct tag *tag;
-
- tag = vorbis_comments_to_tag(comments);
- if (!tag)
- return;
-
- decoder_tag(decoder, is, tag);
- tag_free(tag);
-}
-
-/* public */
-static void
-vorbis_stream_decode(struct decoder *decoder,
- struct input_stream *input_stream)
-{
- GError *error = NULL;
- OggVorbis_File vf;
- struct vorbis_input_stream vis;
- struct audio_format audio_format;
- float total_time;
- int current_section;
- int prev_section = -1;
- long ret;
- char chunk[OGG_CHUNK_SIZE];
- long bitRate = 0;
- long test;
- const vorbis_info *vi;
- enum decoder_command cmd = DECODE_COMMAND_NONE;
-
- if (ogg_stream_type_detect(input_stream) != VORBIS)
- return;
-
- /* rewind the stream, because ogg_stream_type_detect() has
- moved it */
- input_stream_lock_seek(input_stream, 0, SEEK_SET, NULL);
-
- if (!vorbis_is_open(&vis, &vf, decoder, input_stream))
- return;
-
- vi = ov_info(&vf, -1);
- if (vi == NULL) {
- g_warning("ov_info() has failed");
- return;
- }
-
- if (!audio_format_init_checked(&audio_format, vi->rate,
- SAMPLE_FORMAT_S16,
- vi->channels, &error)) {
- g_warning("%s", error->message);
- g_error_free(error);
- return;
- }
-
- total_time = ov_time_total(&vf, -1);
- if (total_time < 0)
- total_time = 0;
-
- decoder_initialized(decoder, &audio_format, vis.seekable, total_time);
-
- do {
- if (cmd == DECODE_COMMAND_SEEK) {
- double seek_where = decoder_seek_where(decoder);
- if (0 == ov_time_seek_page(&vf, seek_where)) {
- decoder_command_finished(decoder);
- } else
- decoder_seek_error(decoder);
- }
-
- ret = ov_read(&vf, chunk, sizeof(chunk),
- OGG_DECODE_USE_BIGENDIAN, 2, 1, &current_section);
- if (ret == OV_HOLE) /* bad packet */
- ret = 0;
- else if (ret <= 0)
- /* break on EOF or other error */
- break;
-
- if (current_section != prev_section) {
- char **comments;
-
- vi = ov_info(&vf, -1);
- if (vi == NULL) {
- g_warning("ov_info() has failed");
- break;
- }
-
- if (vi->rate != (long)audio_format.sample_rate ||
- vi->channels != (int)audio_format.channels) {
- /* we don't support audio format
- change yet */
- g_warning("audio format change, stopping here");
- break;
- }
-
- comments = ov_comment(&vf, -1)->user_comments;
- vorbis_send_comments(decoder, input_stream, comments);
-
- struct replay_gain_info rgi;
- if (vorbis_comments_to_replay_gain(&rgi, comments))
- decoder_replay_gain(decoder, &rgi);
-
- prev_section = current_section;
- }
-
- if ((test = ov_bitrate_instant(&vf)) > 0)
- bitRate = test / 1000;
-
- cmd = decoder_data(decoder, input_stream,
- chunk, ret,
- bitRate);
- } while (cmd != DECODE_COMMAND_STOP);
-
- ov_clear(&vf);
-}
-
-static bool
-vorbis_scan_stream(struct input_stream *is,
- const struct tag_handler *handler, void *handler_ctx)
-{
- struct vorbis_input_stream vis;
- OggVorbis_File vf;
-
- if (!vorbis_is_open(&vis, &vf, NULL, is))
- return false;
-
- tag_handler_invoke_duration(handler, handler_ctx,
- (int)(ov_time_total(&vf, -1) + 0.5));
-
- vorbis_comments_scan(ov_comment(&vf, -1)->user_comments,
- handler, handler_ctx);
-
- ov_clear(&vf);
- return true;
-}
-
-static const char *const vorbis_suffixes[] = {
- "ogg", "oga", NULL
-};
-
-static const char *const vorbis_mime_types[] = {
- "application/ogg",
- "application/x-ogg",
- "audio/ogg",
- "audio/vorbis",
- "audio/vorbis+ogg",
- "audio/x-ogg",
- "audio/x-vorbis",
- "audio/x-vorbis+ogg",
- NULL
-};
-
-const struct decoder_plugin vorbis_decoder_plugin = {
- .name = "vorbis",
- .stream_decode = vorbis_stream_decode,
- .scan_stream = vorbis_scan_stream,
- .suffixes = vorbis_suffixes,
- .mime_types = vorbis_mime_types
-};
diff --git a/src/decoder/wavpack_decoder_plugin.c b/src/decoder/wavpack_decoder_plugin.c
deleted file mode 100644
index 9ebd0fccc..000000000
--- a/src/decoder/wavpack_decoder_plugin.c
+++ /dev/null
@@ -1,596 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "decoder_api.h"
-#include "audio_check.h"
-#include "path.h"
-#include "utils.h"
-#include "tag_table.h"
-#include "tag_handler.h"
-#include "tag_ape.h"
-
-#include <wavpack/wavpack.h>
-#include <glib.h>
-
-#include <assert.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "wavpack"
-
-#define ERRORLEN 80
-
-/** A pointer type for format converter function. */
-typedef void (*format_samples_t)(
- int bytes_per_sample,
- void *buffer, uint32_t count
-);
-
-/*
- * This function has been borrowed from the tiny player found on
- * wavpack.com. Modifications were required because mpd only handles
- * max 24-bit samples.
- */
-static void
-format_samples_int(int bytes_per_sample, void *buffer, uint32_t count)
-{
- int32_t *src = buffer;
-
- switch (bytes_per_sample) {
- case 1: {
- int8_t *dst = buffer;
- /*
- * The asserts like the following one are because we do the
- * formatting of samples within a single buffer. The size
- * of the output samples never can be greater than the size
- * of the input ones. Otherwise we would have an overflow.
- */
- assert_static(sizeof(*dst) <= sizeof(*src));
-
- /* pass through and align 8-bit samples */
- while (count--) {
- *dst++ = *src++;
- }
- break;
- }
- case 2: {
- uint16_t *dst = buffer;
- assert_static(sizeof(*dst) <= sizeof(*src));
-
- /* pass through and align 16-bit samples */
- while (count--) {
- *dst++ = *src++;
- }
- break;
- }
-
- case 3:
- case 4:
- /* do nothing */
- break;
- }
-}
-
-/*
- * This function converts floating point sample data to 24-bit integer.
- */
-static void
-format_samples_float(G_GNUC_UNUSED int bytes_per_sample, void *buffer,
- uint32_t count)
-{
- float *p = buffer;
-
- while (count--) {
- *p /= (1 << 23);
- ++p;
- }
-}
-
-/**
- * Choose a MPD sample format from libwavpacks' number of bits.
- */
-static enum sample_format
-wavpack_bits_to_sample_format(bool is_float, int bytes_per_sample)
-{
- if (is_float)
- return SAMPLE_FORMAT_FLOAT;
-
- switch (bytes_per_sample) {
- case 1:
- return SAMPLE_FORMAT_S8;
-
- case 2:
- return SAMPLE_FORMAT_S16;
-
- case 3:
- return SAMPLE_FORMAT_S24_P32;
-
- case 4:
- return SAMPLE_FORMAT_S32;
-
- default:
- return SAMPLE_FORMAT_UNDEFINED;
- }
-}
-
-/*
- * This does the main decoding thing.
- * Requires an already opened WavpackContext.
- */
-static void
-wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek)
-{
- GError *error = NULL;
- bool is_float;
- enum sample_format sample_format;
- struct audio_format audio_format;
- format_samples_t format_samples;
- float total_time;
- int bytes_per_sample, output_sample_size;
-
- is_float = (WavpackGetMode(wpc) & MODE_FLOAT) != 0;
- sample_format =
- wavpack_bits_to_sample_format(is_float,
- WavpackGetBytesPerSample(wpc));
-
- if (!audio_format_init_checked(&audio_format,
- WavpackGetSampleRate(wpc),
- sample_format,
- WavpackGetNumChannels(wpc), &error)) {
- g_warning("%s", error->message);
- g_error_free(error);
- return;
- }
-
- if (is_float) {
- format_samples = format_samples_float;
- } else {
- format_samples = format_samples_int;
- }
-
- total_time = WavpackGetNumSamples(wpc);
- total_time /= audio_format.sample_rate;
- bytes_per_sample = WavpackGetBytesPerSample(wpc);
- output_sample_size = audio_format_frame_size(&audio_format);
-
- /* wavpack gives us all kind of samples in a 32-bit space */
- int32_t chunk[1024];
- const uint32_t samples_requested = G_N_ELEMENTS(chunk) /
- audio_format.channels;
-
- decoder_initialized(decoder, &audio_format, can_seek, total_time);
-
- enum decoder_command cmd = decoder_get_command(decoder);
- while (cmd != DECODE_COMMAND_STOP) {
- if (cmd == DECODE_COMMAND_SEEK) {
- if (can_seek) {
- unsigned where = decoder_seek_where(decoder) *
- audio_format.sample_rate;
-
- if (WavpackSeekSample(wpc, where)) {
- decoder_command_finished(decoder);
- } else {
- decoder_seek_error(decoder);
- }
- } else {
- decoder_seek_error(decoder);
- }
- }
-
- uint32_t samples_got = WavpackUnpackSamples(wpc, chunk,
- samples_requested);
- if (samples_got == 0)
- break;
-
- int bitrate = (int)(WavpackGetInstantBitrate(wpc) / 1000 +
- 0.5);
- format_samples(bytes_per_sample, chunk,
- samples_got * audio_format.channels);
-
- cmd = decoder_data(decoder, NULL, chunk,
- samples_got * output_sample_size,
- bitrate);
- }
-}
-
-/**
- * Locate and parse a floating point tag. Returns true if it was
- * found.
- */
-static bool
-wavpack_tag_float(WavpackContext *wpc, const char *key, float *value_r)
-{
- char buffer[64];
- int ret;
-
- ret = WavpackGetTagItem(wpc, key, buffer, sizeof(buffer));
- if (ret <= 0)
- return false;
-
- *value_r = atof(buffer);
- return true;
-}
-
-static bool
-wavpack_replaygain(struct replay_gain_info *replay_gain_info,
- WavpackContext *wpc)
-{
- bool found = false;
-
- replay_gain_info_init(replay_gain_info);
-
- found |= wavpack_tag_float(
- wpc, "replaygain_track_gain",
- &replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain
- );
- found |= wavpack_tag_float(
- wpc, "replaygain_track_peak",
- &replay_gain_info->tuples[REPLAY_GAIN_TRACK].peak
- );
- found |= wavpack_tag_float(
- wpc, "replaygain_album_gain",
- &replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain
- );
- found |= wavpack_tag_float(
- wpc, "replaygain_album_peak",
- &replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak
- );
-
- return found;
-}
-
-static void
-wavpack_scan_tag_item(WavpackContext *wpc, const char *name,
- enum tag_type type,
- const struct tag_handler *handler, void *handler_ctx)
-{
- char buffer[1024];
- int len = WavpackGetTagItem(wpc, name, buffer, sizeof(buffer));
- if (len <= 0 || (unsigned)len >= sizeof(buffer))
- return;
-
- tag_handler_invoke_tag(handler, handler_ctx, type, buffer);
-
-}
-
-static void
-wavpack_scan_pair(WavpackContext *wpc, const char *name,
- const struct tag_handler *handler, void *handler_ctx)
-{
- char buffer[8192];
- int len = WavpackGetTagItem(wpc, name, buffer, sizeof(buffer));
- if (len <= 0 || (unsigned)len >= sizeof(buffer))
- return;
-
- tag_handler_invoke_pair(handler, handler_ctx, name, buffer);
-}
-
-/*
- * Reads metainfo from the specified file.
- */
-static bool
-wavpack_scan_file(const char *fname,
- const struct tag_handler *handler, void *handler_ctx)
-{
- WavpackContext *wpc;
- char error[ERRORLEN];
-
- wpc = WavpackOpenFileInput(fname, error, OPEN_TAGS, 0);
- if (wpc == NULL) {
- g_warning(
- "failed to open WavPack file \"%s\": %s\n",
- fname, error
- );
- return false;
- }
-
- tag_handler_invoke_duration(handler, handler_ctx,
- WavpackGetNumSamples(wpc) /
- WavpackGetSampleRate(wpc));
-
- /* the WavPack format implies APEv2 tags, which means we can
- reuse the mapping from tag_ape.c */
-
- for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) {
- const char *name = tag_item_names[i];
- if (name != NULL)
- wavpack_scan_tag_item(wpc, name, (enum tag_type)i,
- handler, handler_ctx);
- }
-
- for (const struct tag_table *i = ape_tags; i->name != NULL; ++i)
- wavpack_scan_tag_item(wpc, i->name, i->type,
- handler, handler_ctx);
-
- if (handler->pair != NULL) {
- char name[64];
-
- for (int i = 0, n = WavpackGetNumTagItems(wpc);
- i < n; ++i) {
- int len = WavpackGetTagItemIndexed(wpc, i, name,
- sizeof(name));
- if (len <= 0 || (unsigned)len >= sizeof(name))
- continue;
-
- wavpack_scan_pair(wpc, name, handler, handler_ctx);
- }
- }
-
- WavpackCloseFile(wpc);
-
- return true;
-}
-
-/*
- * mpd input_stream <=> WavpackStreamReader wrapper callbacks
- */
-
-/* This struct is needed for per-stream last_byte storage. */
-struct wavpack_input {
- struct decoder *decoder;
- struct input_stream *is;
- /* Needed for push_back_byte() */
- int last_byte;
-};
-
-/**
- * Little wrapper for struct wavpack_input to cast from void *.
- */
-static struct wavpack_input *
-wpin(void *id)
-{
- assert(id);
- return id;
-}
-
-static int32_t
-wavpack_input_read_bytes(void *id, void *data, int32_t bcount)
-{
- uint8_t *buf = (uint8_t *)data;
- int32_t i = 0;
-
- if (wpin(id)->last_byte != EOF) {
- *buf++ = wpin(id)->last_byte;
- wpin(id)->last_byte = EOF;
- --bcount;
- ++i;
- }
-
- /* wavpack fails if we return a partial read, so we just wait
- until the buffer is full */
- while (bcount > 0) {
- size_t nbytes = decoder_read(
- wpin(id)->decoder, wpin(id)->is, buf, bcount
- );
- if (nbytes == 0) {
- /* EOF, error or a decoder command */
- break;
- }
-
- i += nbytes;
- bcount -= nbytes;
- buf += nbytes;
- }
-
- return i;
-}
-
-static uint32_t
-wavpack_input_get_pos(void *id)
-{
- return wpin(id)->is->offset;
-}
-
-static int
-wavpack_input_set_pos_abs(void *id, uint32_t pos)
-{
- return input_stream_lock_seek(wpin(id)->is, pos, SEEK_SET, NULL)
- ? 0 : -1;
-}
-
-static int
-wavpack_input_set_pos_rel(void *id, int32_t delta, int mode)
-{
- return input_stream_lock_seek(wpin(id)->is, delta, mode, NULL)
- ? 0 : -1;
-}
-
-static int
-wavpack_input_push_back_byte(void *id, int c)
-{
- if (wpin(id)->last_byte == EOF) {
- wpin(id)->last_byte = c;
- return c;
- } else {
- return EOF;
- }
-}
-
-static uint32_t
-wavpack_input_get_length(void *id)
-{
- if (wpin(id)->is->size < 0)
- return 0;
-
- return wpin(id)->is->size;
-}
-
-static int
-wavpack_input_can_seek(void *id)
-{
- return wpin(id)->is->seekable;
-}
-
-static WavpackStreamReader mpd_is_reader = {
- .read_bytes = wavpack_input_read_bytes,
- .get_pos = wavpack_input_get_pos,
- .set_pos_abs = wavpack_input_set_pos_abs,
- .set_pos_rel = wavpack_input_set_pos_rel,
- .push_back_byte = wavpack_input_push_back_byte,
- .get_length = wavpack_input_get_length,
- .can_seek = wavpack_input_can_seek,
- .write_bytes = NULL /* no need to write edited tags */
-};
-
-static void
-wavpack_input_init(struct wavpack_input *isp, struct decoder *decoder,
- struct input_stream *is)
-{
- isp->decoder = decoder;
- isp->is = is;
- isp->last_byte = EOF;
-}
-
-static struct input_stream *
-wavpack_open_wvc(struct decoder *decoder, const char *uri,
- GMutex *mutex, GCond *cond,
- struct wavpack_input *wpi)
-{
- struct input_stream *is_wvc;
- char *wvc_url = NULL;
- char first_byte;
- size_t nbytes;
-
- /*
- * As we use dc->utf8url, this function will be bad for
- * single files. utf8url is not absolute file path :/
- */
- if (uri == NULL)
- return false;
-
- wvc_url = g_strconcat(uri, "c", NULL);
- is_wvc = input_stream_open(wvc_url, mutex, cond, NULL);
- g_free(wvc_url);
-
- if (is_wvc == NULL)
- return NULL;
-
- /*
- * And we try to buffer in order to get know
- * about a possible 404 error.
- */
- nbytes = decoder_read(
- decoder, is_wvc, &first_byte, sizeof(first_byte)
- );
- if (nbytes == 0) {
- input_stream_close(is_wvc);
- return NULL;
- }
-
- /* push it back */
- wavpack_input_init(wpi, decoder, is_wvc);
- wpi->last_byte = first_byte;
- return is_wvc;
-}
-
-/*
- * Decodes a stream.
- */
-static void
-wavpack_streamdecode(struct decoder * decoder, struct input_stream *is)
-{
- char error[ERRORLEN];
- WavpackContext *wpc;
- struct input_stream *is_wvc;
- int open_flags = OPEN_NORMALIZE;
- struct wavpack_input isp, isp_wvc;
- bool can_seek = is->seekable;
-
- is_wvc = wavpack_open_wvc(decoder, is->uri, is->mutex, is->cond,
- &isp_wvc);
- if (is_wvc != NULL) {
- open_flags |= OPEN_WVC;
- can_seek &= is_wvc->seekable;
- }
-
- if (!can_seek) {
- open_flags |= OPEN_STREAMING;
- }
-
- wavpack_input_init(&isp, decoder, is);
- wpc = WavpackOpenFileInputEx(
- &mpd_is_reader, &isp,
- open_flags & OPEN_WVC ? &isp_wvc : NULL,
- error, open_flags, 23
- );
-
- if (wpc == NULL) {
- g_warning("failed to open WavPack stream: %s\n", error);
- return;
- }
-
- wavpack_decode(decoder, wpc, can_seek);
-
- WavpackCloseFile(wpc);
- if (open_flags & OPEN_WVC) {
- input_stream_close(is_wvc);
- }
-}
-
-/*
- * Decodes a file.
- */
-static void
-wavpack_filedecode(struct decoder *decoder, const char *fname)
-{
- char error[ERRORLEN];
- WavpackContext *wpc;
-
- wpc = WavpackOpenFileInput(
- fname, error,
- OPEN_TAGS | OPEN_WVC | OPEN_NORMALIZE, 23
- );
- if (wpc == NULL) {
- g_warning(
- "failed to open WavPack file \"%s\": %s\n",
- fname, error
- );
- return;
- }
-
- struct replay_gain_info replay_gain_info;
- if (wavpack_replaygain(&replay_gain_info, wpc))
- decoder_replay_gain(decoder, &replay_gain_info);
-
- wavpack_decode(decoder, wpc, true);
-
- WavpackCloseFile(wpc);
-}
-
-static char const *const wavpack_suffixes[] = {
- "wv",
- NULL
-};
-
-static char const *const wavpack_mime_types[] = {
- "audio/x-wavpack",
- NULL
-};
-
-const struct decoder_plugin wavpack_decoder_plugin = {
- .name = "wavpack",
- .stream_decode = wavpack_streamdecode,
- .file_decode = wavpack_filedecode,
- .scan_file = wavpack_scan_file,
- .suffixes = wavpack_suffixes,
- .mime_types = wavpack_mime_types
-};
diff --git a/src/decoder/wildmidi_decoder_plugin.c b/src/decoder/wildmidi_decoder_plugin.c
deleted file mode 100644
index 2cdb30a9c..000000000
--- a/src/decoder/wildmidi_decoder_plugin.c
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "decoder_api.h"
-#include "tag_handler.h"
-#include "glib_compat.h"
-
-#include <glib.h>
-
-#include <wildmidi_lib.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "wildmidi"
-
-enum {
- WILDMIDI_SAMPLE_RATE = 48000,
-};
-
-static bool
-wildmidi_init(const struct config_param *param)
-{
- const char *config_file;
- int ret;
-
- config_file = config_get_block_string(param, "config_file",
- "/etc/timidity/timidity.cfg");
- if (!g_file_test(config_file, G_FILE_TEST_IS_REGULAR)) {
- g_debug("configuration file does not exist: %s", config_file);
- return false;
- }
-
- ret = WildMidi_Init(config_file, WILDMIDI_SAMPLE_RATE, 0);
- return ret == 0;
-}
-
-static void
-wildmidi_finish(void)
-{
- WildMidi_Shutdown();
-}
-
-static void
-wildmidi_file_decode(struct decoder *decoder, const char *path_fs)
-{
- static const struct audio_format audio_format = {
- .sample_rate = WILDMIDI_SAMPLE_RATE,
- .format = SAMPLE_FORMAT_S16,
- .channels = 2,
- };
- midi *wm;
- const struct _WM_Info *info;
- enum decoder_command cmd;
-
- wm = WildMidi_Open(path_fs);
- if (wm == NULL)
- return;
-
- info = WildMidi_GetInfo(wm);
- if (info == NULL) {
- WildMidi_Close(wm);
- return;
- }
-
- decoder_initialized(decoder, &audio_format, true,
- info->approx_total_samples / WILDMIDI_SAMPLE_RATE);
-
- do {
- char buffer[4096];
- int len;
-
- info = WildMidi_GetInfo(wm);
- if (info == NULL)
- break;
-
- len = WildMidi_GetOutput(wm, buffer, sizeof(buffer));
- if (len <= 0)
- break;
-
- cmd = decoder_data(decoder, NULL, buffer, len, 0);
-
- if (cmd == DECODE_COMMAND_SEEK) {
- unsigned long seek_where = WILDMIDI_SAMPLE_RATE *
- decoder_seek_where(decoder);
-
-#ifdef HAVE_WILDMIDI_SAMPLED_SEEK
- WildMidi_SampledSeek(wm, &seek_where);
-#else
- WildMidi_FastSeek(wm, &seek_where);
-#endif
- decoder_command_finished(decoder);
- cmd = DECODE_COMMAND_NONE;
- }
-
- } while (cmd == DECODE_COMMAND_NONE);
-
- WildMidi_Close(wm);
-}
-
-static bool
-wildmidi_scan_file(const char *path_fs,
- const struct tag_handler *handler, void *handler_ctx)
-{
- midi *wm = WildMidi_Open(path_fs);
- if (wm == NULL)
- return false;
-
- const struct _WM_Info *info = WildMidi_GetInfo(wm);
- if (info == NULL) {
- WildMidi_Close(wm);
- return false;
- }
-
- int duration = info->approx_total_samples / WILDMIDI_SAMPLE_RATE;
- tag_handler_invoke_duration(handler, handler_ctx, duration);
-
- WildMidi_Close(wm);
-
- return true;
-}
-
-static const char *const wildmidi_suffixes[] = {
- "mid",
- NULL
-};
-
-const struct decoder_plugin wildmidi_decoder_plugin = {
- .name = "wildmidi",
- .init = wildmidi_init,
- .finish = wildmidi_finish,
- .file_decode = wildmidi_file_decode,
- .scan_file = wildmidi_scan_file,
- .suffixes = wildmidi_suffixes,
-};
diff --git a/src/decoder_api.c b/src/decoder_api.c
deleted file mode 100644
index a45d0f1e6..000000000
--- a/src/decoder_api.c
+++ /dev/null
@@ -1,567 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "decoder_api.h"
-#include "decoder_internal.h"
-#include "decoder_control.h"
-#include "audio_config.h"
-#include "song.h"
-#include "buffer.h"
-#include "pipe.h"
-#include "chunk.h"
-#include "replay_gain_config.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <stdlib.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "decoder"
-
-void
-decoder_initialized(struct decoder *decoder,
- const struct audio_format *audio_format,
- bool seekable, float total_time)
-{
- struct decoder_control *dc = decoder->dc;
- struct audio_format_string af_string;
-
- assert(dc->state == DECODE_STATE_START);
- assert(dc->pipe != NULL);
- assert(decoder != NULL);
- assert(decoder->stream_tag == NULL);
- assert(decoder->decoder_tag == NULL);
- assert(!decoder->seeking);
- assert(audio_format != NULL);
- assert(audio_format_defined(audio_format));
- assert(audio_format_valid(audio_format));
-
- dc->in_audio_format = *audio_format;
- getOutputAudioFormat(audio_format, &dc->out_audio_format);
-
- dc->seekable = seekable;
- dc->total_time = total_time;
-
- decoder_lock(dc);
- dc->state = DECODE_STATE_DECODE;
- g_cond_signal(dc->client_cond);
- decoder_unlock(dc);
-
- g_debug("audio_format=%s, seekable=%s",
- audio_format_to_string(&dc->in_audio_format, &af_string),
- seekable ? "true" : "false");
-
- if (!audio_format_equals(&dc->in_audio_format,
- &dc->out_audio_format))
- g_debug("converting to %s",
- audio_format_to_string(&dc->out_audio_format,
- &af_string));
-}
-
-/**
- * Checks if we need an "initial seek". If so, then the initial seek
- * is prepared, and the function returns true.
- */
-G_GNUC_PURE
-static bool
-decoder_prepare_initial_seek(struct decoder *decoder)
-{
- const struct decoder_control *dc = decoder->dc;
- assert(dc->pipe != NULL);
-
- if (dc->state != DECODE_STATE_DECODE)
- /* wait until the decoder has finished initialisation
- (reading file headers etc.) before emitting the
- virtual "SEEK" command */
- return false;
-
- if (decoder->initial_seek_running)
- /* initial seek has already begun - override any other
- command */
- return true;
-
- if (decoder->initial_seek_pending) {
- if (!dc->seekable) {
- /* seeking is not possible */
- decoder->initial_seek_pending = false;
- return false;
- }
-
- if (dc->command == DECODE_COMMAND_NONE) {
- /* begin initial seek */
-
- decoder->initial_seek_pending = false;
- decoder->initial_seek_running = true;
- return true;
- }
-
- /* skip initial seek when there's another command
- (e.g. STOP) */
-
- decoder->initial_seek_pending = false;
- }
-
- return false;
-}
-
-/**
- * Returns the current decoder command. May return a "virtual"
- * synthesized command, e.g. to seek to the beginning of the CUE
- * track.
- */
-G_GNUC_PURE
-static enum decoder_command
-decoder_get_virtual_command(struct decoder *decoder)
-{
- const struct decoder_control *dc = decoder->dc;
- assert(dc->pipe != NULL);
-
- if (decoder_prepare_initial_seek(decoder))
- return DECODE_COMMAND_SEEK;
-
- return dc->command;
-}
-
-enum decoder_command
-decoder_get_command(struct decoder *decoder)
-{
- return decoder_get_virtual_command(decoder);
-}
-
-void
-decoder_command_finished(struct decoder *decoder)
-{
- struct decoder_control *dc = decoder->dc;
-
- decoder_lock(dc);
-
- assert(dc->command != DECODE_COMMAND_NONE ||
- decoder->initial_seek_running);
- assert(dc->command != DECODE_COMMAND_SEEK ||
- decoder->initial_seek_running ||
- dc->seek_error || decoder->seeking);
- assert(dc->pipe != NULL);
-
- if (decoder->initial_seek_running) {
- assert(!decoder->seeking);
- assert(decoder->chunk == NULL);
- assert(music_pipe_empty(dc->pipe));
-
- decoder->initial_seek_running = false;
- decoder->timestamp = dc->start_ms / 1000.;
- decoder_unlock(dc);
- return;
- }
-
- if (decoder->seeking) {
- decoder->seeking = false;
-
- /* delete frames from the old song position */
-
- if (decoder->chunk != NULL) {
- music_buffer_return(dc->buffer, decoder->chunk);
- decoder->chunk = NULL;
- }
-
- music_pipe_clear(dc->pipe, dc->buffer);
-
- decoder->timestamp = dc->seek_where;
- }
-
- dc->command = DECODE_COMMAND_NONE;
- g_cond_signal(dc->client_cond);
- decoder_unlock(dc);
-}
-
-double decoder_seek_where(G_GNUC_UNUSED struct decoder * decoder)
-{
- const struct decoder_control *dc = decoder->dc;
-
- assert(dc->pipe != NULL);
-
- if (decoder->initial_seek_running)
- return dc->start_ms / 1000.;
-
- assert(dc->command == DECODE_COMMAND_SEEK);
-
- decoder->seeking = true;
-
- return dc->seek_where;
-}
-
-void decoder_seek_error(struct decoder * decoder)
-{
- struct decoder_control *dc = decoder->dc;
-
- assert(dc->pipe != NULL);
-
- if (decoder->initial_seek_running) {
- /* d'oh, we can't seek to the sub-song start position,
- what now? - no idea, ignoring the problem for now. */
- decoder->initial_seek_running = false;
- return;
- }
-
- assert(dc->command == DECODE_COMMAND_SEEK);
-
- dc->seek_error = true;
- decoder->seeking = false;
-
- decoder_command_finished(decoder);
-}
-
-/**
- * Should be read operation be cancelled? That is the case when the
- * player thread has sent a command such as "STOP".
- */
-G_GNUC_PURE
-static inline bool
-decoder_check_cancel_read(const struct decoder *decoder)
-{
- if (decoder == NULL)
- return false;
-
- const struct decoder_control *dc = decoder->dc;
- if (dc->command == DECODE_COMMAND_NONE)
- return false;
-
- /* ignore the SEEK command during initialization, the plugin
- should handle that after it has initialized successfully */
- if (dc->command == DECODE_COMMAND_SEEK &&
- (dc->state == DECODE_STATE_START || decoder->seeking))
- return false;
-
- return true;
-}
-
-size_t decoder_read(struct decoder *decoder,
- struct input_stream *is,
- void *buffer, size_t length)
-{
- /* XXX don't allow decoder==NULL */
- GError *error = NULL;
- size_t nbytes;
-
- assert(decoder == NULL ||
- decoder->dc->state == DECODE_STATE_START ||
- decoder->dc->state == DECODE_STATE_DECODE);
- assert(is != NULL);
- assert(buffer != NULL);
-
- if (length == 0)
- return 0;
-
- input_stream_lock(is);
-
- while (true) {
- if (decoder_check_cancel_read(decoder)) {
- input_stream_unlock(is);
- return 0;
- }
-
- if (input_stream_available(is))
- break;
-
- g_cond_wait(is->cond, is->mutex);
- }
-
- nbytes = input_stream_read(is, buffer, length, &error);
- assert(nbytes == 0 || error == NULL);
- assert(nbytes > 0 || error != NULL || input_stream_eof(is));
-
- if (G_UNLIKELY(nbytes == 0 && error != NULL)) {
- g_warning("%s", error->message);
- g_error_free(error);
- }
-
- input_stream_unlock(is);
-
- return nbytes;
-}
-
-void
-decoder_timestamp(struct decoder *decoder, double t)
-{
- assert(decoder != NULL);
- assert(t >= 0);
-
- decoder->timestamp = t;
-}
-
-/**
- * Sends a #tag as-is to the music pipe. Flushes the current chunk
- * (decoder.chunk) if there is one.
- */
-static enum decoder_command
-do_send_tag(struct decoder *decoder, const struct tag *tag)
-{
- struct music_chunk *chunk;
-
- if (decoder->chunk != NULL) {
- /* there is a partial chunk - flush it, we want the
- tag in a new chunk */
- decoder_flush_chunk(decoder);
- g_cond_signal(decoder->dc->client_cond);
- }
-
- assert(decoder->chunk == NULL);
-
- chunk = decoder_get_chunk(decoder);
- if (chunk == NULL) {
- assert(decoder->dc->command != DECODE_COMMAND_NONE);
- return decoder->dc->command;
- }
-
- chunk->tag = tag_dup(tag);
- return DECODE_COMMAND_NONE;
-}
-
-static bool
-update_stream_tag(struct decoder *decoder, struct input_stream *is)
-{
- struct tag *tag;
-
- tag = is != NULL
- ? input_stream_lock_tag(is)
- : NULL;
- if (tag == NULL) {
- tag = decoder->song_tag;
- if (tag == NULL)
- return false;
-
- /* no stream tag present - submit the song tag
- instead */
- decoder->song_tag = NULL;
- }
-
- if (decoder->stream_tag != NULL)
- tag_free(decoder->stream_tag);
-
- decoder->stream_tag = tag;
- return true;
-}
-
-enum decoder_command
-decoder_data(struct decoder *decoder,
- struct input_stream *is,
- const void *_data, size_t length,
- uint16_t kbit_rate)
-{
- struct decoder_control *dc = decoder->dc;
- const char *data = _data;
- GError *error = NULL;
- enum decoder_command cmd;
-
- assert(dc->state == DECODE_STATE_DECODE);
- assert(dc->pipe != NULL);
- assert(length % audio_format_frame_size(&dc->in_audio_format) == 0);
-
- decoder_lock(dc);
- cmd = decoder_get_virtual_command(decoder);
- decoder_unlock(dc);
-
- if (cmd == DECODE_COMMAND_STOP || cmd == DECODE_COMMAND_SEEK ||
- length == 0)
- return cmd;
-
- /* send stream tags */
-
- if (update_stream_tag(decoder, is)) {
- if (decoder->decoder_tag != NULL) {
- /* merge with tag from decoder plugin */
- struct tag *tag;
-
- tag = tag_merge(decoder->decoder_tag,
- decoder->stream_tag);
- cmd = do_send_tag(decoder, tag);
- tag_free(tag);
- } else
- /* send only the stream tag */
- cmd = do_send_tag(decoder, decoder->stream_tag);
-
- if (cmd != DECODE_COMMAND_NONE)
- return cmd;
- }
-
- if (!audio_format_equals(&dc->in_audio_format, &dc->out_audio_format)) {
- data = pcm_convert(&decoder->conv_state,
- &dc->in_audio_format, data, length,
- &dc->out_audio_format, &length,
- &error);
- if (data == NULL) {
- /* the PCM conversion has failed - stop
- playback, since we have no better way to
- bail out */
- g_warning("%s", error->message);
- return DECODE_COMMAND_STOP;
- }
- }
-
- while (length > 0) {
- struct music_chunk *chunk;
- char *dest;
- size_t nbytes;
- bool full;
-
- chunk = decoder_get_chunk(decoder);
- if (chunk == NULL) {
- assert(dc->command != DECODE_COMMAND_NONE);
- return dc->command;
- }
-
- dest = music_chunk_write(chunk, &dc->out_audio_format,
- decoder->timestamp -
- dc->song->start_ms / 1000.0,
- kbit_rate, &nbytes);
- if (dest == NULL) {
- /* the chunk is full, flush it */
- decoder_flush_chunk(decoder);
- g_cond_signal(dc->client_cond);
- continue;
- }
-
- assert(nbytes > 0);
-
- if (nbytes > length)
- nbytes = length;
-
- /* copy the buffer */
-
- memcpy(dest, data, nbytes);
-
- /* expand the music pipe chunk */
-
- full = music_chunk_expand(chunk, &dc->out_audio_format, nbytes);
- if (full) {
- /* the chunk is full, flush it */
- decoder_flush_chunk(decoder);
- g_cond_signal(dc->client_cond);
- }
-
- data += nbytes;
- length -= nbytes;
-
- decoder->timestamp += (double)nbytes /
- audio_format_time_to_size(&dc->out_audio_format);
-
- if (dc->end_ms > 0 &&
- decoder->timestamp >= dc->end_ms / 1000.0)
- /* the end of this range has been reached:
- stop decoding */
- return DECODE_COMMAND_STOP;
- }
-
- return DECODE_COMMAND_NONE;
-}
-
-enum decoder_command
-decoder_tag(G_GNUC_UNUSED struct decoder *decoder, struct input_stream *is,
- const struct tag *tag)
-{
- G_GNUC_UNUSED const struct decoder_control *dc = decoder->dc;
- enum decoder_command cmd;
-
- assert(dc->state == DECODE_STATE_DECODE);
- assert(dc->pipe != NULL);
- assert(tag != NULL);
-
- /* save the tag */
-
- if (decoder->decoder_tag != NULL)
- tag_free(decoder->decoder_tag);
- decoder->decoder_tag = tag_dup(tag);
-
- /* check for a new stream tag */
-
- update_stream_tag(decoder, is);
-
- /* check if we're seeking */
-
- if (decoder_prepare_initial_seek(decoder))
- /* during initial seek, no music chunk must be created
- until seeking is finished; skip the rest of the
- function here */
- return DECODE_COMMAND_SEEK;
-
- /* send tag to music pipe */
-
- if (decoder->stream_tag != NULL) {
- /* merge with tag from input stream */
- struct tag *merged;
-
- merged = tag_merge(decoder->stream_tag, decoder->decoder_tag);
- cmd = do_send_tag(decoder, merged);
- tag_free(merged);
- } else
- /* send only the decoder tag */
- cmd = do_send_tag(decoder, tag);
-
- return cmd;
-}
-
-float
-decoder_replay_gain(struct decoder *decoder,
- const struct replay_gain_info *replay_gain_info)
-{
- float return_db = 0;
- assert(decoder != NULL);
-
- if (replay_gain_info != NULL) {
- static unsigned serial;
- if (++serial == 0)
- serial = 1;
-
- if (REPLAY_GAIN_OFF != replay_gain_mode) {
- return_db = 20.0 * log10f(
- replay_gain_tuple_scale(
- &replay_gain_info->tuples[replay_gain_get_real_mode()],
- replay_gain_preamp, replay_gain_missing_preamp,
- replay_gain_limit));
- }
-
- decoder->replay_gain_info = *replay_gain_info;
- decoder->replay_gain_serial = serial;
-
- if (decoder->chunk != NULL) {
- /* flush the current chunk because the new
- replay gain values affect the following
- samples */
- decoder_flush_chunk(decoder);
- g_cond_signal(decoder->dc->client_cond);
- }
- } else
- decoder->replay_gain_serial = 0;
-
- return return_db;
-}
-
-void
-decoder_mixramp(struct decoder *decoder, float replay_gain_db,
- char *mixramp_start, char *mixramp_end)
-{
- assert(decoder != NULL);
- struct decoder_control *dc = decoder->dc;
- assert(dc != NULL);
-
- dc->replay_gain_db = replay_gain_db;
- dc_mixramp_start(dc, mixramp_start);
- dc_mixramp_end(dc, mixramp_end);
-}
diff --git a/src/decoder_api.h b/src/decoder_api.h
deleted file mode 100644
index 6e011c395..000000000
--- a/src/decoder_api.h
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * 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.
- */
-
-/*! \file
- * \brief The MPD Decoder API
- *
- * This is the public API which is used by decoder plugins to
- * communicate with the mpd core.
- */
-
-#ifndef MPD_DECODER_API_H
-#define MPD_DECODER_API_H
-
-#include "check.h"
-#include "decoder_command.h"
-#include "decoder_plugin.h"
-#include "input_stream.h"
-#include "replay_gain_info.h"
-#include "tag.h"
-#include "audio_format.h"
-#include "conf.h"
-
-#include <stdbool.h>
-
-/**
- * Notify the player thread that it has finished initialization and
- * that it has read the song's meta data.
- *
- * @param decoder the decoder object
- * @param audio_format the audio format which is going to be sent to
- * decoder_data()
- * @param seekable true if the song is seekable
- * @param total_time the total number of seconds in this song; -1 if unknown
- */
-void
-decoder_initialized(struct decoder *decoder,
- const struct audio_format *audio_format,
- bool seekable, float total_time);
-
-/**
- * Determines the pending decoder command.
- *
- * @param decoder the decoder object
- * @return the current command, or DECODE_COMMAND_NONE if there is no
- * command pending
- */
-enum decoder_command
-decoder_get_command(struct decoder *decoder);
-
-/**
- * Called by the decoder when it has performed the requested command
- * (dc->command). This function resets dc->command and wakes up the
- * player thread.
- *
- * @param decoder the decoder object
- */
-void
-decoder_command_finished(struct decoder *decoder);
-
-/**
- * Call this when you have received the DECODE_COMMAND_SEEK command.
- *
- * @param decoder the decoder object
- * @return the destination position for the week
- */
-double
-decoder_seek_where(struct decoder *decoder);
-
-/**
- * Call this instead of decoder_command_finished() when seeking has
- * failed.
- *
- * @param decoder the decoder object
- */
-void
-decoder_seek_error(struct decoder *decoder);
-
-/**
- * Blocking read from the input stream.
- *
- * @param decoder the decoder object
- * @param is the input stream to read from
- * @param buffer the destination buffer
- * @param length the maximum number of bytes to read
- * @return the number of bytes read, or 0 if one of the following
- * occurs: end of file; error; command (like SEEK or STOP).
- */
-size_t
-decoder_read(struct decoder *decoder, struct input_stream *is,
- void *buffer, size_t length);
-
-/**
- * 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
- * on the buffer size won't work.
- */
-void
-decoder_timestamp(struct decoder *decoder, double t);
-
-/**
- * This function is called by the decoder plugin when it has
- * successfully decoded block of input data.
- *
- * @param decoder the decoder object
- * @param is an input stream which is buffering while we are waiting
- * for the player
- * @param data the source buffer
- * @param length the number of bytes in the buffer
- * @return the current command, or DECODE_COMMAND_NONE if there is no
- * command pending
- */
-enum decoder_command
-decoder_data(struct decoder *decoder, struct input_stream *is,
- const void *data, size_t length,
- uint16_t kbit_rate);
-
-/**
- * This function is called by the decoder plugin when it has
- * successfully decoded a tag.
- *
- * @param decoder the decoder object
- * @param is an input stream which is buffering while we are waiting
- * for the player
- * @param tag the tag to send
- * @return the current command, or DECODE_COMMAND_NONE if there is no
- * command pending
- */
-enum decoder_command
-decoder_tag(struct decoder *decoder, struct input_stream *is,
- const struct tag *tag);
-
-/**
- * Set replay gain values for the following chunks.
- *
- * @param decoder the decoder object
- * @param rgi the replay_gain_info object; may be NULL to invalidate
- * the previous replay gain values
- * @return the replay gain adjustment used
- */
-float
-decoder_replay_gain(struct decoder *decoder,
- const struct replay_gain_info *replay_gain_info);
-
-/**
- * Store MixRamp tags.
- *
- * @param decoder the decoder object
- * @param replay_gain_db the ReplayGain adjustment used for this song
- * @param mixramp_start the mixramp_start tag; may be NULL to invalidate
- * @param mixramp_end the mixramp_end tag; may be NULL to invalidate
- */
-void
-decoder_mixramp(struct decoder *decoder, float replay_gain_db,
- char *mixramp_start, char *mixramp_end);
-
-#endif
diff --git a/src/decoder_buffer.c b/src/decoder_buffer.c
deleted file mode 100644
index fcb135976..000000000
--- a/src/decoder_buffer.c
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "decoder_buffer.h"
-#include "decoder_api.h"
-
-#include <glib.h>
-
-#include <assert.h>
-
-struct decoder_buffer {
- struct decoder *decoder;
- struct input_stream *is;
-
- /** the allocated size of the buffer */
- size_t size;
-
- /** the current length of the buffer */
- size_t length;
-
- /** number of bytes already consumed at the beginning of the
- buffer */
- size_t consumed;
-
- /** the actual buffer (dynamic size) */
- unsigned char data[sizeof(size_t)];
-};
-
-struct decoder_buffer *
-decoder_buffer_new(struct decoder *decoder, struct input_stream *is,
- size_t size)
-{
- struct decoder_buffer *buffer =
- g_malloc(sizeof(*buffer) - sizeof(buffer->data) + size);
-
- assert(is != NULL);
- assert(size > 0);
-
- buffer->decoder = decoder;
- buffer->is = is;
- buffer->size = size;
- buffer->length = 0;
- buffer->consumed = 0;
-
- return buffer;
-}
-
-void
-decoder_buffer_free(struct decoder_buffer *buffer)
-{
- assert(buffer != NULL);
-
- g_free(buffer);
-}
-
-bool
-decoder_buffer_is_empty(const struct decoder_buffer *buffer)
-{
- return buffer->consumed == buffer->length;
-}
-
-bool
-decoder_buffer_is_full(const struct decoder_buffer *buffer)
-{
- return buffer->consumed == 0 && buffer->length == buffer->size;
-}
-
-static void
-decoder_buffer_shift(struct decoder_buffer *buffer)
-{
- assert(buffer->consumed > 0);
-
- buffer->length -= buffer->consumed;
- memmove(buffer->data, buffer->data + buffer->consumed, buffer->length);
- buffer->consumed = 0;
-}
-
-bool
-decoder_buffer_fill(struct decoder_buffer *buffer)
-{
- size_t nbytes;
-
- if (buffer->consumed > 0)
- decoder_buffer_shift(buffer);
-
- if (buffer->length >= buffer->size)
- /* buffer is full */
- return false;
-
- nbytes = decoder_read(buffer->decoder, buffer->is,
- buffer->data + buffer->length,
- buffer->size - buffer->length);
- if (nbytes == 0)
- /* end of file, I/O error or decoder command
- received */
- return false;
-
- buffer->length += nbytes;
- assert(buffer->length <= buffer->size);
-
- return true;
-}
-
-const void *
-decoder_buffer_read(const struct decoder_buffer *buffer, size_t *length_r)
-{
- if (buffer->consumed >= buffer->length)
- /* buffer is empty */
- return NULL;
-
- *length_r = buffer->length - buffer->consumed;
- return buffer->data + buffer->consumed;
-}
-
-void
-decoder_buffer_consume(struct decoder_buffer *buffer, size_t nbytes)
-{
- /* just move the "consumed" pointer - decoder_buffer_shift()
- will do the real work later (called by
- decoder_buffer_fill()) */
- buffer->consumed += nbytes;
-
- assert(buffer->consumed <= buffer->length);
-}
-
-bool
-decoder_buffer_skip(struct decoder_buffer *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 != NULL) {
- if (length > nbytes)
- length = nbytes;
- decoder_buffer_consume(buffer, length);
- nbytes -= length;
- if (nbytes == 0)
- return true;
- }
-
- success = decoder_buffer_fill(buffer);
- if (!success)
- return false;
- }
-}
diff --git a/src/decoder_buffer.h b/src/decoder_buffer.h
deleted file mode 100644
index 77eff5dd1..000000000
--- a/src/decoder_buffer.h
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * 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_DECODER_BUFFER_H
-#define MPD_DECODER_BUFFER_H
-
-#include <stdbool.h>
-#include <stddef.h>
-
-/**
- * This objects handles buffered reads in decoder plugins easily. You
- * create a buffer object, and use its high-level methods to fill and
- * read it. It will automatically handle shifting the buffer.
- */
-struct decoder_buffer;
-
-struct decoder;
-struct input_stream;
-
-/**
- * Creates a new buffer.
- *
- * @param decoder the decoder object, used for decoder_read(), may be NULL
- * @param is the input stream object where we should read from
- * @param size the maximum size of the buffer
- * @return the new decoder_buffer object
- */
-struct decoder_buffer *
-decoder_buffer_new(struct decoder *decoder, struct input_stream *is,
- size_t size);
-
-/**
- * Frees resources used by the decoder_buffer object.
- */
-void
-decoder_buffer_free(struct decoder_buffer *buffer);
-
-bool
-decoder_buffer_is_empty(const struct decoder_buffer *buffer);
-
-bool
-decoder_buffer_is_full(const struct decoder_buffer *buffer);
-
-/**
- * Read data from the input_stream and append it to the buffer.
- *
- * @return true if data was appended; false if there is no data
- * available (yet), end of file, I/O error or a decoder command was
- * received
- */
-bool
-decoder_buffer_fill(struct decoder_buffer *buffer);
-
-/**
- * Reads data from the buffer. This data is not yet consumed, you
- * have to call decoder_buffer_consume() to do that. The returned
- * buffer becomes invalid after a decoder_buffer_fill() or a
- * 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 NULL if there is no data
- * available
- */
-const void *
-decoder_buffer_read(const struct decoder_buffer *buffer, size_t *length_r);
-
-/**
- * Consume (delete, invalidate) a part of the buffer. The "nbytes"
- * parameter must not be larger than the length returned by
- * decoder_buffer_read().
- *
- * @param buffer the decoder_buffer object
- * @param nbytes the number of bytes to consume
- */
-void
-decoder_buffer_consume(struct decoder_buffer *buffer, size_t nbytes);
-
-/**
- * Skips the specified number of bytes, discarding its data.
- *
- * @param buffer the decoder_buffer object
- * @param nbytes the number of bytes to skip
- * @return true on success, false on error
- */
-bool
-decoder_buffer_skip(struct decoder_buffer *buffer, size_t nbytes);
-
-#endif
diff --git a/src/decoder_command.h b/src/decoder_command.h
deleted file mode 100644
index 795e13fb2..000000000
--- a/src/decoder_command.h
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * 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_DECODER_COMMAND_H
-#define MPD_DECODER_COMMAND_H
-
-enum decoder_command {
- DECODE_COMMAND_NONE = 0,
- DECODE_COMMAND_START,
- DECODE_COMMAND_STOP,
- DECODE_COMMAND_SEEK
-};
-
-#endif
diff --git a/src/decoder_control.c b/src/decoder_control.c
deleted file mode 100644
index 2ce03b666..000000000
--- a/src/decoder_control.c
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "decoder_control.h"
-#include "pipe.h"
-
-#include <assert.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "decoder_control"
-
-struct decoder_control *
-dc_new(GCond *client_cond)
-{
- struct decoder_control *dc = g_new(struct decoder_control, 1);
-
- dc->thread = NULL;
-
- dc->mutex = g_mutex_new();
- dc->cond = g_cond_new();
- dc->client_cond = client_cond;
-
- dc->state = DECODE_STATE_STOP;
- dc->command = DECODE_COMMAND_NONE;
-
- dc->replay_gain_db = 0;
- dc->replay_gain_prev_db = 0;
- dc->mixramp_start = NULL;
- dc->mixramp_end = NULL;
- dc->mixramp_prev_end = NULL;
-
- return dc;
-}
-
-void
-dc_free(struct decoder_control *dc)
-{
- g_cond_free(dc->cond);
- g_mutex_free(dc->mutex);
- g_free(dc->mixramp_start);
- g_free(dc->mixramp_end);
- g_free(dc->mixramp_prev_end);
- g_free(dc);
-}
-
-static void
-dc_command_wait_locked(struct decoder_control *dc)
-{
- while (dc->command != DECODE_COMMAND_NONE)
- g_cond_wait(dc->client_cond, dc->mutex);
-}
-
-static void
-dc_command_locked(struct decoder_control *dc, enum decoder_command cmd)
-{
- dc->command = cmd;
- decoder_signal(dc);
- dc_command_wait_locked(dc);
-}
-
-static void
-dc_command(struct decoder_control *dc, enum decoder_command cmd)
-{
- decoder_lock(dc);
- dc_command_locked(dc, cmd);
- decoder_unlock(dc);
-}
-
-static void
-dc_command_async(struct decoder_control *dc, enum decoder_command cmd)
-{
- decoder_lock(dc);
-
- dc->command = cmd;
- decoder_signal(dc);
-
- decoder_unlock(dc);
-}
-
-void
-dc_start(struct decoder_control *dc, struct song *song,
- unsigned start_ms, unsigned end_ms,
- struct music_buffer *buffer, struct music_pipe *pipe)
-{
- assert(song != NULL);
- assert(buffer != NULL);
- assert(pipe != NULL);
- assert(music_pipe_empty(pipe));
-
- dc->song = song;
- dc->start_ms = start_ms;
- dc->end_ms = end_ms;
- dc->buffer = buffer;
- dc->pipe = pipe;
- dc_command(dc, DECODE_COMMAND_START);
-}
-
-void
-dc_stop(struct decoder_control *dc)
-{
- decoder_lock(dc);
-
- if (dc->command != DECODE_COMMAND_NONE)
- /* Attempt to cancel the current command. If it's too
- late and the decoder thread is already executing
- the old command, we'll call STOP again in this
- function (see below). */
- dc_command_locked(dc, DECODE_COMMAND_STOP);
-
- if (dc->state != DECODE_STATE_STOP && dc->state != DECODE_STATE_ERROR)
- dc_command_locked(dc, DECODE_COMMAND_STOP);
-
- decoder_unlock(dc);
-}
-
-bool
-dc_seek(struct decoder_control *dc, double where)
-{
- assert(dc->state != DECODE_STATE_START);
- assert(where >= 0.0);
-
- if (dc->state == DECODE_STATE_STOP ||
- dc->state == DECODE_STATE_ERROR || !dc->seekable)
- return false;
-
- dc->seek_where = where;
- dc->seek_error = false;
- dc_command(dc, DECODE_COMMAND_SEEK);
-
- if (dc->seek_error)
- return false;
-
- return true;
-}
-
-void
-dc_quit(struct decoder_control *dc)
-{
- assert(dc->thread != NULL);
-
- dc->quit = true;
- dc_command_async(dc, DECODE_COMMAND_STOP);
-
- g_thread_join(dc->thread);
- dc->thread = NULL;
-}
-
-void
-dc_mixramp_start(struct decoder_control *dc, char *mixramp_start)
-{
- assert(dc != NULL);
-
- g_free(dc->mixramp_start);
- dc->mixramp_start = mixramp_start;
-}
-
-void
-dc_mixramp_end(struct decoder_control *dc, char *mixramp_end)
-{
- assert(dc != NULL);
-
- g_free(dc->mixramp_end);
- dc->mixramp_end = mixramp_end;
-}
-
-void
-dc_mixramp_prev_end(struct decoder_control *dc, char *mixramp_prev_end)
-{
- assert(dc != NULL);
-
- g_free(dc->mixramp_prev_end);
- dc->mixramp_prev_end = mixramp_prev_end;
-}
diff --git a/src/decoder_control.h b/src/decoder_control.h
deleted file mode 100644
index 566b153ee..000000000
--- a/src/decoder_control.h
+++ /dev/null
@@ -1,277 +0,0 @@
-/*
- * 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_DECODER_CONTROL_H
-#define MPD_DECODER_CONTROL_H
-
-#include "decoder_command.h"
-#include "audio_format.h"
-
-#include <glib.h>
-
-#include <assert.h>
-
-enum decoder_state {
- DECODE_STATE_STOP = 0,
- DECODE_STATE_START,
- DECODE_STATE_DECODE,
-
- /**
- * The last "START" command failed, because there was an I/O
- * error or because no decoder was able to decode the file.
- * This state will only come after START; once the state has
- * turned to DECODE, by definition no such error can occur.
- */
- DECODE_STATE_ERROR,
-};
-
-struct decoder_control {
- /** the handle of the decoder thread, or NULL if the decoder
- thread isn't running */
- GThread *thread;
-
- /**
- * This lock protects #state and #command.
- */
- GMutex *mutex;
-
- /**
- * Trigger this object after you have modified #command. This
- * is also used by the decoder thread to notify the caller
- * when it has finished a command.
- */
- GCond *cond;
-
- /**
- * The trigger of this object's client. It is signalled
- * whenever an event occurs.
- */
- GCond *client_cond;
-
- enum decoder_state state;
- enum decoder_command command;
-
- bool quit;
- bool seek_error;
- bool seekable;
- double seek_where;
-
- /** the format of the song file */
- struct audio_format in_audio_format;
-
- /** the format being sent to the music pipe */
- struct audio_format out_audio_format;
-
- /**
- * The song currently being decoded. This attribute is set by
- * the player thread, when it sends the #DECODE_COMMAND_START
- * command.
- */
- const struct song *song;
-
- /**
- * The initial seek position (in milliseconds), e.g. to the
- * start of a sub-track described by a CUE file.
- *
- * This attribute is set by dc_start().
- */
- unsigned start_ms;
-
- /**
- * The decoder will stop when it reaches this position (in
- * milliseconds). 0 means don't stop before the end of the
- * file.
- *
- * This attribute is set by dc_start().
- */
- unsigned end_ms;
-
- float total_time;
-
- /** the #music_chunk allocator */
- struct music_buffer *buffer;
-
- /**
- * The destination pipe for decoded chunks. The caller thread
- * owns this object, and is responsible for freeing it.
- */
- struct music_pipe *pipe;
-
- float replay_gain_db;
- float replay_gain_prev_db;
- char *mixramp_start;
- char *mixramp_end;
- char *mixramp_prev_end;
-};
-
-G_GNUC_MALLOC
-struct decoder_control *
-dc_new(GCond *client_cond);
-
-void
-dc_free(struct decoder_control *dc);
-
-/**
- * Locks the #decoder_control object.
- */
-static inline void
-decoder_lock(struct decoder_control *dc)
-{
- g_mutex_lock(dc->mutex);
-}
-
-/**
- * Unlocks the #decoder_control object.
- */
-static inline void
-decoder_unlock(struct decoder_control *dc)
-{
- g_mutex_unlock(dc->mutex);
-}
-
-/**
- * Waits for a signal on the #decoder_control object. This function
- * is only valid in the decoder thread. The object must be locked
- * prior to calling this function.
- */
-static inline void
-decoder_wait(struct decoder_control *dc)
-{
- g_cond_wait(dc->cond, dc->mutex);
-}
-
-/**
- * Signals the #decoder_control object. This function is only valid
- * in the player thread. The object should be locked prior to calling
- * this function.
- */
-static inline void
-decoder_signal(struct decoder_control *dc)
-{
- g_cond_signal(dc->cond);
-}
-
-static inline bool
-decoder_is_idle(const struct decoder_control *dc)
-{
- return dc->state == DECODE_STATE_STOP ||
- dc->state == DECODE_STATE_ERROR;
-}
-
-static inline bool
-decoder_is_starting(const struct decoder_control *dc)
-{
- return dc->state == DECODE_STATE_START;
-}
-
-static inline bool
-decoder_has_failed(const struct decoder_control *dc)
-{
- assert(dc->command == DECODE_COMMAND_NONE);
-
- return dc->state == DECODE_STATE_ERROR;
-}
-
-static inline bool
-decoder_lock_is_idle(struct decoder_control *dc)
-{
- bool ret;
-
- decoder_lock(dc);
- ret = decoder_is_idle(dc);
- decoder_unlock(dc);
-
- return ret;
-}
-
-static inline bool
-decoder_lock_is_starting(struct decoder_control *dc)
-{
- bool ret;
-
- decoder_lock(dc);
- ret = decoder_is_starting(dc);
- decoder_unlock(dc);
-
- return ret;
-}
-
-static inline bool
-decoder_lock_has_failed(struct decoder_control *dc)
-{
- bool ret;
-
- decoder_lock(dc);
- ret = decoder_has_failed(dc);
- decoder_unlock(dc);
-
- return ret;
-}
-
-static inline const struct song *
-decoder_current_song(const struct decoder_control *dc)
-{
- switch (dc->state) {
- case DECODE_STATE_STOP:
- case DECODE_STATE_ERROR:
- return NULL;
-
- case DECODE_STATE_START:
- case DECODE_STATE_DECODE:
- return dc->song;
- }
-
- assert(false);
- return NULL;
-}
-
-/**
- * Start the decoder.
- *
- * @param the decoder
- * @param song the song to be decoded
- * @param start_ms see #decoder_control
- * @param end_ms see #decoder_control
- * @param pipe the pipe which receives the decoded chunks (owned by
- * the caller)
- */
-void
-dc_start(struct decoder_control *dc, struct song *song,
- unsigned start_ms, unsigned end_ms,
- struct music_buffer *buffer, struct music_pipe *pipe);
-
-void
-dc_stop(struct decoder_control *dc);
-
-bool
-dc_seek(struct decoder_control *dc, double where);
-
-void
-dc_quit(struct decoder_control *dc);
-
-void
-dc_mixramp_start(struct decoder_control *dc, char *mixramp_start);
-
-void
-dc_mixramp_end(struct decoder_control *dc, char *mixramp_end);
-
-void
-dc_mixramp_prev_end(struct decoder_control *dc, char *mixramp_prev_end);
-
-#endif
diff --git a/src/decoder_internal.c b/src/decoder_internal.c
deleted file mode 100644
index bc349f2ff..000000000
--- a/src/decoder_internal.c
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "decoder_internal.h"
-#include "decoder_control.h"
-#include "pipe.h"
-#include "input_stream.h"
-#include "buffer.h"
-#include "chunk.h"
-
-#include <assert.h>
-
-/**
- * All chunks are full of decoded data; wait for the player to free
- * one.
- */
-static enum decoder_command
-need_chunks(struct decoder_control *dc, bool do_wait)
-{
- if (dc->command == DECODE_COMMAND_STOP ||
- dc->command == DECODE_COMMAND_SEEK)
- return dc->command;
-
- if (do_wait) {
- decoder_wait(dc);
- g_cond_signal(dc->client_cond);
-
- return dc->command;
- }
-
- return DECODE_COMMAND_NONE;
-}
-
-struct music_chunk *
-decoder_get_chunk(struct decoder *decoder)
-{
- struct decoder_control *dc = decoder->dc;
- enum decoder_command cmd;
-
- assert(decoder != NULL);
-
- if (decoder->chunk != NULL)
- return decoder->chunk;
-
- do {
- decoder->chunk = music_buffer_allocate(dc->buffer);
- if (decoder->chunk != NULL) {
- 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;
- }
-
- decoder_lock(dc);
- cmd = need_chunks(dc, true);
- decoder_unlock(dc);
- } while (cmd == DECODE_COMMAND_NONE);
-
- return NULL;
-}
-
-void
-decoder_flush_chunk(struct decoder *decoder)
-{
- struct decoder_control *dc = decoder->dc;
-
- assert(decoder != NULL);
- assert(decoder->chunk != NULL);
-
- if (music_chunk_is_empty(decoder->chunk))
- music_buffer_return(dc->buffer, decoder->chunk);
- else
- music_pipe_push(dc->pipe, decoder->chunk);
-
- decoder->chunk = NULL;
-}
diff --git a/src/decoder_internal.h b/src/decoder_internal.h
deleted file mode 100644
index d89e68cfc..000000000
--- a/src/decoder_internal.h
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * 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_DECODER_INTERNAL_H
-#define MPD_DECODER_INTERNAL_H
-
-#include "decoder_command.h"
-#include "pcm_convert.h"
-#include "replay_gain_info.h"
-
-struct input_stream;
-
-struct decoder {
- struct decoder_control *dc;
-
- struct pcm_convert_state conv_state;
-
- /**
- * The time stamp of the next data chunk, in seconds.
- */
- double timestamp;
-
- /**
- * Is the initial seek (to the start position of the sub-song)
- * pending, or has it been performed already?
- */
- bool initial_seek_pending;
-
- /**
- * Is the initial seek currently running? During this time,
- * the decoder command is SEEK. This flag is set by
- * decoder_get_virtual_command(), when the virtual SEEK
- * command is generated for the first time.
- */
- bool initial_seek_running;
-
- /**
- * This flag is set by decoder_seek_where(), and checked by
- * decoder_command_finished(). It is used to clean up after
- * seeking.
- */
- bool seeking;
-
- /**
- * The tag from the song object. This is only used for local
- * files, because we expect the stream server to send us a new
- * tag each time we play it.
- */
- struct tag *song_tag;
-
- /** the last tag received from the stream */
- struct tag *stream_tag;
-
- /** the last tag received from the decoder plugin */
- struct tag *decoder_tag;
-
- /** the chunk currently being written to */
- struct music_chunk *chunk;
-
- struct replay_gain_info replay_gain_info;
-
- /**
- * A positive serial number for checking if replay gain info
- * has changed since the last check.
- */
- unsigned replay_gain_serial;
-};
-
-/**
- * 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(struct decoder *decoder);
-
-/**
- * Flushes the current chunk.
- */
-void
-decoder_flush_chunk(struct decoder *decoder);
-
-#endif
diff --git a/src/decoder_list.c b/src/decoder_list.c
deleted file mode 100644
index 177b632ad..000000000
--- a/src/decoder_list.c
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
- * 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 "decoder_list.h"
-#include "decoder_plugin.h"
-#include "utils.h"
-#include "conf.h"
-#include "mpd_error.h"
-#include "decoder/pcm_decoder_plugin.h"
-#include "decoder/dsdiff_decoder_plugin.h"
-#include "decoder/dsf_decoder_plugin.h"
-
-#include <glib.h>
-
-#include <string.h>
-
-extern const struct decoder_plugin mad_decoder_plugin;
-extern const struct decoder_plugin mpg123_decoder_plugin;
-extern const struct decoder_plugin vorbis_decoder_plugin;
-extern const struct decoder_plugin flac_decoder_plugin;
-extern const struct decoder_plugin oggflac_decoder_plugin;
-extern const struct decoder_plugin sndfile_decoder_plugin;
-extern const struct decoder_plugin audiofile_decoder_plugin;
-extern const struct decoder_plugin mp4ff_decoder_plugin;
-extern const struct decoder_plugin faad_decoder_plugin;
-extern const struct decoder_plugin mpcdec_decoder_plugin;
-extern const struct decoder_plugin wavpack_decoder_plugin;
-extern const struct decoder_plugin modplug_decoder_plugin;
-extern const struct decoder_plugin mikmod_decoder_plugin;
-extern const struct decoder_plugin sidplay_decoder_plugin;
-extern const struct decoder_plugin wildmidi_decoder_plugin;
-extern const struct decoder_plugin fluidsynth_decoder_plugin;
-extern const struct decoder_plugin ffmpeg_decoder_plugin;
-extern const struct decoder_plugin gme_decoder_plugin;
-
-const struct decoder_plugin *const decoder_plugins[] = {
-#ifdef HAVE_MAD
- &mad_decoder_plugin,
-#endif
-#ifdef HAVE_MPG123
- &mpg123_decoder_plugin,
-#endif
-#ifdef ENABLE_VORBIS_DECODER
- &vorbis_decoder_plugin,
-#endif
-#if defined(HAVE_FLAC)
- &oggflac_decoder_plugin,
-#endif
-#ifdef HAVE_FLAC
- &flac_decoder_plugin,
-#endif
-#ifdef ENABLE_SNDFILE
- &sndfile_decoder_plugin,
-#endif
-#ifdef HAVE_AUDIOFILE
- &audiofile_decoder_plugin,
-#endif
- &dsdiff_decoder_plugin,
- &dsf_decoder_plugin,
-#ifdef HAVE_FAAD
- &faad_decoder_plugin,
-#endif
-#ifdef HAVE_MP4
- &mp4ff_decoder_plugin,
-#endif
-#ifdef HAVE_MPCDEC
- &mpcdec_decoder_plugin,
-#endif
-#ifdef HAVE_WAVPACK
- &wavpack_decoder_plugin,
-#endif
-#ifdef HAVE_MODPLUG
- &modplug_decoder_plugin,
-#endif
-#ifdef ENABLE_MIKMOD_DECODER
- &mikmod_decoder_plugin,
-#endif
-#ifdef ENABLE_SIDPLAY
- &sidplay_decoder_plugin,
-#endif
-#ifdef ENABLE_WILDMIDI
- &wildmidi_decoder_plugin,
-#endif
-#ifdef ENABLE_FLUIDSYNTH
- &fluidsynth_decoder_plugin,
-#endif
-#ifdef HAVE_FFMPEG
- &ffmpeg_decoder_plugin,
-#endif
-#ifdef HAVE_GME
- &gme_decoder_plugin,
-#endif
- &pcm_decoder_plugin,
- NULL
-};
-
-enum {
- num_decoder_plugins = G_N_ELEMENTS(decoder_plugins) - 1,
-};
-
-/** which plugins have been initialized successfully? */
-bool decoder_plugins_enabled[num_decoder_plugins];
-
-static unsigned
-decoder_plugin_index(const struct decoder_plugin *plugin)
-{
- unsigned i = 0;
-
- while (decoder_plugins[i] != plugin)
- ++i;
-
- return i;
-}
-
-static unsigned
-decoder_plugin_next_index(const struct decoder_plugin *plugin)
-{
- return plugin == 0
- ? 0 /* start with first plugin */
- : decoder_plugin_index(plugin) + 1;
-}
-
-const struct decoder_plugin *
-decoder_plugin_from_suffix(const char *suffix,
- const struct decoder_plugin *plugin)
-{
- if (suffix == NULL)
- return NULL;
-
- for (unsigned i = decoder_plugin_next_index(plugin);
- decoder_plugins[i] != NULL; ++i) {
- plugin = decoder_plugins[i];
- if (decoder_plugins_enabled[i] &&
- decoder_plugin_supports_suffix(plugin, suffix))
- return plugin;
- }
-
- return NULL;
-}
-
-const struct decoder_plugin *
-decoder_plugin_from_mime_type(const char *mimeType, unsigned int next)
-{
- static unsigned i = num_decoder_plugins;
-
- if (mimeType == NULL)
- return NULL;
-
- if (!next)
- i = 0;
- for (; decoder_plugins[i] != NULL; ++i) {
- const struct decoder_plugin *plugin = decoder_plugins[i];
- if (decoder_plugins_enabled[i] &&
- decoder_plugin_supports_mime_type(plugin, mimeType)) {
- ++i;
- return plugin;
- }
- }
-
- return NULL;
-}
-
-const struct decoder_plugin *
-decoder_plugin_from_name(const char *name)
-{
- decoder_plugins_for_each_enabled(plugin)
- if (strcmp(plugin->name, name) == 0)
- return plugin;
-
- return NULL;
-}
-
-/**
- * Find the "decoder" configuration block for the specified plugin.
- *
- * @param plugin_name the name of the decoder plugin
- * @return the configuration block, or NULL if none was configured
- */
-static const struct config_param *
-decoder_plugin_config(const char *plugin_name)
-{
- const struct config_param *param = NULL;
-
- while ((param = config_get_next_param(CONF_DECODER, param)) != NULL) {
- const char *name =
- config_get_block_string(param, "plugin", NULL);
- if (name == NULL)
- MPD_ERROR("decoder configuration without 'plugin' name in line %d",
- param->line);
-
- if (strcmp(name, plugin_name) == 0)
- return param;
- }
-
- return NULL;
-}
-
-void decoder_plugin_init_all(void)
-{
- for (unsigned i = 0; decoder_plugins[i] != NULL; ++i) {
- const struct decoder_plugin *plugin = decoder_plugins[i];
- const struct config_param *param =
- decoder_plugin_config(plugin->name);
-
- if (!config_get_block_bool(param, "enabled", true))
- /* the plugin is disabled in mpd.conf */
- continue;
-
- if (decoder_plugin_init(plugin, param))
- decoder_plugins_enabled[i] = true;
- }
-}
-
-void decoder_plugin_deinit_all(void)
-{
- decoder_plugins_for_each_enabled(plugin)
- decoder_plugin_finish(plugin);
-}
diff --git a/src/decoder_list.h b/src/decoder_list.h
deleted file mode 100644
index d0a6ade7e..000000000
--- a/src/decoder_list.h
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * 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_DECODER_LIST_H
-#define MPD_DECODER_LIST_H
-
-#include <stdbool.h>
-
-struct decoder_plugin;
-
-extern const struct decoder_plugin *const decoder_plugins[];
-extern bool decoder_plugins_enabled[];
-
-#define decoder_plugins_for_each(plugin) \
- for (const struct decoder_plugin *plugin, \
- *const*decoder_plugin_iterator = &decoder_plugins[0]; \
- (plugin = *decoder_plugin_iterator) != NULL; \
- ++decoder_plugin_iterator)
-
-#define decoder_plugins_for_each_enabled(plugin) \
- decoder_plugins_for_each(plugin) \
- if (decoder_plugins_enabled[decoder_plugin_iterator - decoder_plugins])
-
-/* 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 NULL to find the first plugin
- * @return a plugin, or NULL if none matches
- */
-const struct decoder_plugin *
-decoder_plugin_from_suffix(const char *suffix,
- const struct decoder_plugin *plugin);
-
-const struct decoder_plugin *
-decoder_plugin_from_mime_type(const char *mimeType, unsigned int next);
-
-const struct decoder_plugin *
-decoder_plugin_from_name(const char *name);
-
-/* this is where we "load" all the "plugins" ;-) */
-void decoder_plugin_init_all(void);
-
-/* this is where we "unload" all the "plugins" */
-void decoder_plugin_deinit_all(void);
-
-#endif
diff --git a/src/decoder_plugin.c b/src/decoder_plugin.c
deleted file mode 100644
index d32043f0e..000000000
--- a/src/decoder_plugin.c
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "decoder_plugin.h"
-#include "string_util.h"
-
-#include <assert.h>
-
-bool
-decoder_plugin_supports_suffix(const struct decoder_plugin *plugin,
- const char *suffix)
-{
- assert(plugin != NULL);
- assert(suffix != NULL);
-
- return plugin->suffixes != NULL &&
- string_array_contains(plugin->suffixes, suffix);
-
-}
-
-bool
-decoder_plugin_supports_mime_type(const struct decoder_plugin *plugin,
- const char *mime_type)
-{
- assert(plugin != NULL);
- assert(mime_type != NULL);
-
- return plugin->mime_types != NULL &&
- string_array_contains(plugin->mime_types, mime_type);
-}
diff --git a/src/decoder_plugin.h b/src/decoder_plugin.h
deleted file mode 100644
index 933ba6751..000000000
--- a/src/decoder_plugin.h
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * 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_DECODER_PLUGIN_H
-#define MPD_DECODER_PLUGIN_H
-
-#include <stdbool.h>
-#include <stddef.h>
-
-struct config_param;
-struct input_stream;
-struct tag;
-struct tag_handler;
-
-/**
- * Opaque handle which the decoder plugin passes to the functions in
- * this header.
- */
-struct decoder;
-
-struct decoder_plugin {
- const char *name;
-
- /**
- * Initialize the decoder plugin. Optional method.
- *
- * @param param a configuration block for this plugin, or NULL
- * if none is configured
- * @return true if the plugin was initialized successfully,
- * false if the plugin is not available
- */
- bool (*init)(const struct config_param *param);
-
- /**
- * Deinitialize a decoder plugin which was initialized
- * successfully. Optional method.
- */
- void (*finish)(void);
-
- /**
- * Decode a stream (data read from an #input_stream object).
- *
- * Either implement this method or file_decode(). If
- * possible, it is recommended to implement this method,
- * because it is more versatile.
- */
- void (*stream_decode)(struct decoder *decoder,
- struct input_stream *is);
-
- /**
- * Decode a local file.
- *
- * Either implement this method or stream_decode().
- */
- void (*file_decode)(struct decoder *decoder, const char *path_fs);
-
- /**
- * Scan metadata of a file.
- *
- * @return false if the operation has failed
- */
- bool (*scan_file)(const char *path_fs,
- const struct tag_handler *handler,
- void *handler_ctx);
-
- /**
- * Scan metadata of a file.
- *
- * @return false if the operation has failed
- */
- bool (*scan_stream)(struct input_stream *is,
- const struct tag_handler *handler,
- void *handler_ctx);
-
- /**
- * @brief Return a "virtual" filename for subtracks in
- * container formats like flac
- * @param const char* pathname full pathname for the file on fs
- * @param const unsigned int tnum track number
- *
- * @return NULL if there are no multiple files
- * a filename for every single track according to tnum (param 2)
- * do not include full pathname here, just the "virtual" file
- */
- char* (*container_scan)(const char *path_fs, const unsigned int tnum);
-
- /* last element in these arrays must always be a NULL: */
- const char *const*suffixes;
- const char *const*mime_types;
-};
-
-/**
- * Initialize a decoder plugin.
- *
- * @param param a configuration block for this plugin, or NULL if none
- * is configured
- * @return true if the plugin was initialized successfully, false if
- * the plugin is not available
- */
-static inline bool
-decoder_plugin_init(const struct decoder_plugin *plugin,
- const struct config_param *param)
-{
- return plugin->init != NULL
- ? plugin->init(param)
- : true;
-}
-
-/**
- * Deinitialize a decoder plugin which was initialized successfully.
- */
-static inline void
-decoder_plugin_finish(const struct decoder_plugin *plugin)
-{
- if (plugin->finish != NULL)
- plugin->finish();
-}
-
-/**
- * Decode a stream.
- */
-static inline void
-decoder_plugin_stream_decode(const struct decoder_plugin *plugin,
- struct decoder *decoder, struct input_stream *is)
-{
- plugin->stream_decode(decoder, is);
-}
-
-/**
- * Decode a file.
- */
-static inline void
-decoder_plugin_file_decode(const struct decoder_plugin *plugin,
- struct decoder *decoder, const char *path_fs)
-{
- plugin->file_decode(decoder, path_fs);
-}
-
-/**
- * Read the tag of a file.
- */
-static inline bool
-decoder_plugin_scan_file(const struct decoder_plugin *plugin,
- const char *path_fs,
- const struct tag_handler *handler, void *handler_ctx)
-{
- return plugin->scan_file != NULL
- ? plugin->scan_file(path_fs, handler, handler_ctx)
- : false;
-}
-
-/**
- * Read the tag of a stream.
- */
-static inline bool
-decoder_plugin_scan_stream(const struct decoder_plugin *plugin,
- struct input_stream *is,
- const struct tag_handler *handler,
- void *handler_ctx)
-{
- return plugin->scan_stream != NULL
- ? plugin->scan_stream(is, handler, handler_ctx)
- : false;
-}
-
-/**
- * return "virtual" tracks in a container
- */
-static inline char *
-decoder_plugin_container_scan( const struct decoder_plugin *plugin,
- const char* pathname,
- const unsigned int tnum)
-{
- return plugin->container_scan(pathname, tnum);
-}
-
-/**
- * Does the plugin announce the specified file name suffix?
- */
-bool
-decoder_plugin_supports_suffix(const struct decoder_plugin *plugin,
- const char *suffix);
-
-/**
- * Does the plugin announce the specified MIME type?
- */
-bool
-decoder_plugin_supports_mime_type(const struct decoder_plugin *plugin,
- const char *mime_type);
-
-#endif
diff --git a/src/decoder_print.c b/src/decoder_print.c
deleted file mode 100644
index e14477ed8..000000000
--- a/src/decoder_print.c
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "decoder_print.h"
-#include "decoder_list.h"
-#include "decoder_plugin.h"
-#include "client.h"
-
-#include <assert.h>
-
-static void
-decoder_plugin_print(struct client *client,
- const struct decoder_plugin *plugin)
-{
- const char *const*p;
-
- assert(plugin != NULL);
- assert(plugin->name != NULL);
-
- client_printf(client, "plugin: %s\n", plugin->name);
-
- if (plugin->suffixes != NULL)
- for (p = plugin->suffixes; *p != NULL; ++p)
- client_printf(client, "suffix: %s\n", *p);
-
- if (plugin->mime_types != NULL)
- for (p = plugin->mime_types; *p != NULL; ++p)
- client_printf(client, "mime_type: %s\n", *p);
-}
-
-void
-decoder_list_print(struct client *client)
-{
- decoder_plugins_for_each_enabled(plugin)
- decoder_plugin_print(client, plugin);
-}
diff --git a/src/decoder_print.h b/src/decoder_print.h
deleted file mode 100644
index 31713d5d8..000000000
--- a/src/decoder_print.h
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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_DECODER_PRINT_H
-#define MPD_DECODER_PRINT_H
-
-struct client;
-
-void
-decoder_list_print(struct client *client);
-
-#endif
diff --git a/src/decoder_thread.c b/src/decoder_thread.c
deleted file mode 100644
index af80ed45b..000000000
--- a/src/decoder_thread.c
+++ /dev/null
@@ -1,510 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "decoder_thread.h"
-#include "decoder_control.h"
-#include "decoder_internal.h"
-#include "decoder_list.h"
-#include "decoder_plugin.h"
-#include "decoder_api.h"
-#include "replay_gain_ape.h"
-#include "input_stream.h"
-#include "pipe.h"
-#include "song.h"
-#include "tag.h"
-#include "mapper.h"
-#include "path.h"
-#include "uri.h"
-#include "mpd_error.h"
-
-#include <glib.h>
-
-#include <unistd.h>
-#include <stdio.h> /* for SEEK_SET */
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "decoder_thread"
-
-/**
- * Marks the current decoder command as "finished" and notifies the
- * player thread.
- *
- * @param dc the #decoder_control object; must be locked
- */
-static void
-decoder_command_finished_locked(struct decoder_control *dc)
-{
- assert(dc->command != DECODE_COMMAND_NONE);
-
- dc->command = DECODE_COMMAND_NONE;
-
- g_cond_signal(dc->client_cond);
-}
-
-/**
- * Opens the input stream with input_stream_open(), and waits until
- * the stream gets ready. If a decoder STOP command is received
- * during that, it cancels the operation (but does not close the
- * stream).
- *
- * Unlock the decoder before calling this function.
- *
- * @return an input_stream on success or if #DECODE_COMMAND_STOP is
- * received, NULL on error
- */
-static struct input_stream *
-decoder_input_stream_open(struct decoder_control *dc, const char *uri)
-{
- GError *error = NULL;
- struct input_stream *is;
-
- is = input_stream_open(uri, dc->mutex, dc->cond, &error);
- if (is == NULL) {
- if (error != NULL) {
- g_warning("%s", error->message);
- g_error_free(error);
- }
-
- return NULL;
- }
-
- /* wait for the input stream to become ready; its metadata
- will be available then */
-
- decoder_lock(dc);
-
- input_stream_update(is);
- while (!is->ready &&
- dc->command != DECODE_COMMAND_STOP) {
- decoder_wait(dc);
-
- input_stream_update(is);
- }
-
- if (!input_stream_check(is, &error)) {
- decoder_unlock(dc);
-
- g_warning("%s", error->message);
- g_error_free(error);
-
- return NULL;
- }
-
- decoder_unlock(dc);
-
- return is;
-}
-
-static bool
-decoder_stream_decode(const struct decoder_plugin *plugin,
- struct decoder *decoder,
- struct input_stream *input_stream)
-{
- assert(plugin != NULL);
- assert(plugin->stream_decode != NULL);
- assert(decoder != NULL);
- assert(decoder->stream_tag == NULL);
- assert(decoder->decoder_tag == NULL);
- assert(input_stream != NULL);
- assert(input_stream->ready);
- assert(decoder->dc->state == DECODE_STATE_START);
-
- g_debug("probing plugin %s", plugin->name);
-
- if (decoder->dc->command == DECODE_COMMAND_STOP)
- return true;
-
- /* rewind the stream, so each plugin gets a fresh start */
- input_stream_seek(input_stream, 0, SEEK_SET, NULL);
-
- decoder_unlock(decoder->dc);
-
- decoder_plugin_stream_decode(plugin, decoder, input_stream);
-
- decoder_lock(decoder->dc);
-
- assert(decoder->dc->state == DECODE_STATE_START ||
- decoder->dc->state == DECODE_STATE_DECODE);
-
- return decoder->dc->state != DECODE_STATE_START;
-}
-
-static bool
-decoder_file_decode(const struct decoder_plugin *plugin,
- struct decoder *decoder, const char *path)
-{
- assert(plugin != NULL);
- assert(plugin->file_decode != NULL);
- assert(decoder != NULL);
- assert(decoder->stream_tag == NULL);
- assert(decoder->decoder_tag == NULL);
- assert(path != NULL);
- assert(g_path_is_absolute(path));
- assert(decoder->dc->state == DECODE_STATE_START);
-
- g_debug("probing plugin %s", plugin->name);
-
- if (decoder->dc->command == DECODE_COMMAND_STOP)
- return true;
-
- decoder_unlock(decoder->dc);
-
- decoder_plugin_file_decode(plugin, decoder, path);
-
- decoder_lock(decoder->dc);
-
- assert(decoder->dc->state == DECODE_STATE_START ||
- decoder->dc->state == DECODE_STATE_DECODE);
-
- return decoder->dc->state != DECODE_STATE_START;
-}
-
-/**
- * Hack to allow tracking const decoder plugins in a GSList.
- */
-static inline gpointer
-deconst_plugin(const struct decoder_plugin *plugin)
-{
- union {
- const struct decoder_plugin *in;
- gpointer out;
- } u = { .in = plugin };
-
- return u.out;
-}
-
-/**
- * Try decoding a stream, using plugins matching the stream's MIME type.
- *
- * @param tried_r a list of plugins which were tried
- */
-static bool
-decoder_run_stream_mime_type(struct decoder *decoder, struct input_stream *is,
- GSList **tried_r)
-{
- assert(tried_r != NULL);
-
- const struct decoder_plugin *plugin;
- unsigned int next = 0;
-
- if (is->mime == NULL)
- return false;
-
- while ((plugin = decoder_plugin_from_mime_type(is->mime, next++))) {
- if (plugin->stream_decode == NULL)
- continue;
-
- if (g_slist_find(*tried_r, plugin) != NULL)
- /* don't try a plugin twice */
- continue;
-
- if (decoder_stream_decode(plugin, decoder, is))
- return true;
-
- *tried_r = g_slist_prepend(*tried_r, deconst_plugin(plugin));
- }
-
- return false;
-}
-
-/**
- * Try decoding a stream, using plugins matching the stream's URI
- * suffix.
- *
- * @param tried_r a list of plugins which were tried
- */
-static bool
-decoder_run_stream_suffix(struct decoder *decoder, struct input_stream *is,
- const char *uri, GSList **tried_r)
-{
- assert(tried_r != NULL);
-
- const char *suffix = uri_get_suffix(uri);
- const struct decoder_plugin *plugin = NULL;
-
- if (suffix == NULL)
- return false;
-
- while ((plugin = decoder_plugin_from_suffix(suffix, plugin)) != NULL) {
- if (plugin->stream_decode == NULL)
- continue;
-
- if (g_slist_find(*tried_r, plugin) != NULL)
- /* don't try a plugin twice */
- continue;
-
- if (decoder_stream_decode(plugin, decoder, is))
- return true;
-
- *tried_r = g_slist_prepend(*tried_r, deconst_plugin(plugin));
- }
-
- return false;
-}
-
-/**
- * Try decoding a stream, using the fallback plugin.
- */
-static bool
-decoder_run_stream_fallback(struct decoder *decoder, struct input_stream *is)
-{
- const struct decoder_plugin *plugin;
-
- plugin = decoder_plugin_from_name("mad");
- return plugin != NULL && plugin->stream_decode != NULL &&
- decoder_stream_decode(plugin, decoder, is);
-}
-
-/**
- * Try decoding a stream.
- */
-static bool
-decoder_run_stream(struct decoder *decoder, const char *uri)
-{
- struct decoder_control *dc = decoder->dc;
- struct input_stream *input_stream;
- bool success;
-
- decoder_unlock(dc);
-
- input_stream = decoder_input_stream_open(dc, uri);
- if (input_stream == NULL) {
- decoder_lock(dc);
- return false;
- }
-
- decoder_lock(dc);
-
- GSList *tried = NULL;
-
- success = dc->command == DECODE_COMMAND_STOP ||
- /* first we try mime types: */
- decoder_run_stream_mime_type(decoder, input_stream, &tried) ||
- /* if that fails, try suffix matching the URL: */
- decoder_run_stream_suffix(decoder, input_stream, uri,
- &tried) ||
- /* fallback to mp3: this is needed for bastard streams
- that don't have a suffix or set the mimeType */
- (tried == NULL &&
- decoder_run_stream_fallback(decoder, input_stream));
-
- g_slist_free(tried);
-
- decoder_unlock(dc);
- input_stream_close(input_stream);
- decoder_lock(dc);
-
- return success;
-}
-
-/**
- * Attempt to load replay gain data, and pass it to
- * decoder_replay_gain().
- */
-static void
-decoder_load_replay_gain(struct decoder *decoder, const char *path_fs)
-{
- struct replay_gain_info info;
- if (replay_gain_ape_read(path_fs, &info))
- decoder_replay_gain(decoder, &info);
-}
-
-/**
- * Try decoding a file.
- */
-static bool
-decoder_run_file(struct decoder *decoder, const char *path_fs)
-{
- struct decoder_control *dc = decoder->dc;
- const char *suffix = uri_get_suffix(path_fs);
- const struct decoder_plugin *plugin = NULL;
-
- if (suffix == NULL)
- return false;
-
- decoder_unlock(dc);
-
- decoder_load_replay_gain(decoder, path_fs);
-
- while ((plugin = decoder_plugin_from_suffix(suffix, plugin)) != NULL) {
- if (plugin->file_decode != NULL) {
- decoder_lock(dc);
-
- if (decoder_file_decode(plugin, decoder, path_fs))
- return true;
-
- decoder_unlock(dc);
- } else if (plugin->stream_decode != NULL) {
- struct input_stream *input_stream;
- bool success;
-
- input_stream = decoder_input_stream_open(dc, path_fs);
- if (input_stream == NULL)
- continue;
-
- decoder_lock(dc);
-
- success = decoder_stream_decode(plugin, decoder,
- input_stream);
-
- decoder_unlock(dc);
-
- input_stream_close(input_stream);
-
- if (success) {
- decoder_lock(dc);
- return true;
- }
- }
- }
-
- decoder_lock(dc);
- return false;
-}
-
-static void
-decoder_run_song(struct decoder_control *dc,
- const struct song *song, const char *uri)
-{
- struct decoder decoder = {
- .dc = dc,
- .initial_seek_pending = dc->start_ms > 0,
- .initial_seek_running = false,
- };
- int ret;
-
- decoder.timestamp = 0.0;
- decoder.seeking = false;
- decoder.song_tag = song->tag != NULL && song_is_file(song)
- ? tag_dup(song->tag) : NULL;
- decoder.stream_tag = NULL;
- decoder.decoder_tag = NULL;
- decoder.chunk = NULL;
-
- dc->state = DECODE_STATE_START;
-
- decoder_command_finished_locked(dc);
-
- pcm_convert_init(&decoder.conv_state);
-
- ret = song_is_file(song)
- ? decoder_run_file(&decoder, uri)
- : decoder_run_stream(&decoder, uri);
-
- decoder_unlock(dc);
-
- pcm_convert_deinit(&decoder.conv_state);
-
- /* flush the last chunk */
-
- if (decoder.chunk != NULL)
- decoder_flush_chunk(&decoder);
-
- if (decoder.song_tag != NULL)
- tag_free(decoder.song_tag);
-
- if (decoder.stream_tag != NULL)
- tag_free(decoder.stream_tag);
-
- if (decoder.decoder_tag != NULL)
- tag_free(decoder.decoder_tag);
-
- decoder_lock(dc);
-
- dc->state = ret ? DECODE_STATE_STOP : DECODE_STATE_ERROR;
-}
-
-static void
-decoder_run(struct decoder_control *dc)
-{
- const struct song *song = dc->song;
- char *uri;
-
- assert(song != NULL);
-
- if (song_is_file(song))
- uri = map_song_fs(song);
- else
- uri = song_get_uri(song);
-
- if (uri == NULL) {
- dc->state = DECODE_STATE_ERROR;
- decoder_command_finished_locked(dc);
- return;
- }
-
- decoder_run_song(dc, song, uri);
- g_free(uri);
-
-}
-
-static gpointer
-decoder_task(gpointer arg)
-{
- struct decoder_control *dc = arg;
-
- decoder_lock(dc);
-
- do {
- assert(dc->state == DECODE_STATE_STOP ||
- dc->state == DECODE_STATE_ERROR);
-
- switch (dc->command) {
- case DECODE_COMMAND_START:
- dc_mixramp_start(dc, NULL);
- dc_mixramp_prev_end(dc, dc->mixramp_end);
- dc->mixramp_end = NULL; /* Don't free, it's copied above. */
- dc->replay_gain_prev_db = dc->replay_gain_db;
- dc->replay_gain_db = 0;
-
- /* fall through */
-
- case DECODE_COMMAND_SEEK:
- decoder_run(dc);
- break;
-
- case DECODE_COMMAND_STOP:
- decoder_command_finished_locked(dc);
- break;
-
- case DECODE_COMMAND_NONE:
- decoder_wait(dc);
- break;
- }
- } while (dc->command != DECODE_COMMAND_NONE || !dc->quit);
-
- decoder_unlock(dc);
-
- return NULL;
-}
-
-void
-decoder_thread_start(struct decoder_control *dc)
-{
- GError *e = NULL;
-
- assert(dc->thread == NULL);
-
- dc->quit = false;
-
- dc->thread = g_thread_create(decoder_task, dc, true, &e);
- if (dc->thread == NULL)
- MPD_ERROR("Failed to spawn decoder task: %s", e->message);
-}
diff --git a/src/decoder_thread.h b/src/decoder_thread.h
deleted file mode 100644
index 78f12a54a..000000000
--- a/src/decoder_thread.h
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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_DECODER_THREAD_H
-#define MPD_DECODER_THREAD_H
-
-struct decoder_control;
-
-void
-decoder_thread_start(struct decoder_control *dc);
-
-#endif
diff --git a/src/despotify_utils.c b/src/despotify_utils.c
deleted file mode 100644
index 7555d105d..000000000
--- a/src/despotify_utils.c
+++ /dev/null
@@ -1,121 +0,0 @@
-#include <glib.h>
-#include <despotify.h>
-
-#include "tag.h"
-#include "conf.h"
-#include "despotify_utils.h"
-
-static struct despotify_session *g_session;
-static void (*registered_callbacks[8])(struct despotify_session *,
- int, void *, void *);
-static void *registered_callback_data[8];
-
-static void callback(struct despotify_session* ds, int sig,
- void* data, G_GNUC_UNUSED void* callback_data)
-{
- size_t i;
-
- for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) {
- void (*cb)(struct despotify_session *, int, void *, void *) = registered_callbacks[i];
- void *cb_data = registered_callback_data[i];
-
- if (cb)
- cb(ds, sig, data, cb_data);
- }
-}
-
-bool mpd_despotify_register_callback(void (*cb)(struct despotify_session *, int, void *, void *),
- void *cb_data)
-{
- size_t i;
-
- for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) {
-
- if (!registered_callbacks[i]) {
- registered_callbacks[i] = cb;
- registered_callback_data[i] = cb_data;
-
- return true;
- }
- }
-
- return false;
-}
-
-void mpd_despotify_unregister_callback(void (*cb)(struct despotify_session *, int, void *, void *))
-{
- size_t i;
-
- for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) {
-
- if (registered_callbacks[i] == cb) {
- registered_callbacks[i] = NULL;
- }
- }
-}
-
-
-struct tag *mpd_despotify_tag_from_track(struct ds_track *track)
-{
- char tracknum[20];
- char comment[80];
- char date[20];
- struct tag *tag;
-
- tag = tag_new();
-
- if (!track->has_meta_data)
- return tag;
-
- g_snprintf(tracknum, sizeof(tracknum), "%d", track->tracknumber);
- g_snprintf(date, sizeof(date), "%d", track->year);
- g_snprintf(comment, sizeof(comment), "Bitrate %d Kbps, %sgeo restricted",
- track->file_bitrate / 1000, track->geo_restricted ? "" : "not ");
- tag_add_item(tag, TAG_TITLE, track->title);
- tag_add_item(tag, TAG_ARTIST, track->artist->name);
- tag_add_item(tag, TAG_TRACK, tracknum);
- tag_add_item(tag, TAG_ALBUM, track->album);
- tag_add_item(tag, TAG_DATE, date);
- tag_add_item(tag, TAG_COMMENT, comment);
- tag->time = track->length / 1000;
-
- return tag;
-}
-
-struct despotify_session *mpd_despotify_get_session(void)
-{
- const char *user;
- const char *passwd;
- bool high_bitrate;
-
- if (g_session)
- return g_session;
-
- user = config_get_string(CONF_DESPOTIFY_USER, NULL);
- passwd = config_get_string(CONF_DESPOTIFY_PASSWORD, NULL);
- high_bitrate = config_get_bool(CONF_DESPOTIFY_HIGH_BITRATE, true);
-
- if (user == NULL || passwd == NULL) {
- g_debug("disabling despotify because account is not configured");
- return NULL;
- }
- if (!despotify_init()) {
- g_debug("Can't initialize despotify\n");
- return false;
- }
-
- g_session = despotify_init_client(callback, NULL,
- high_bitrate, true);
- if (!g_session) {
- g_debug("Can't initialize despotify client\n");
- return false;
- }
-
- if (!despotify_authenticate(g_session, user, passwd)) {
- g_debug("Can't authenticate despotify session\n");
- despotify_exit(g_session);
- return false;
- }
-
- return g_session;
-}
diff --git a/src/despotify_utils.h b/src/despotify_utils.h
deleted file mode 100644
index 7e35edc3c..000000000
--- a/src/despotify_utils.h
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * 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_DESPOTIFY_H
-#define MPD_DESPOTIFY_H
-
-struct despotify_session;
-struct ds_track;
-
-/**
- * Return the current despotify session.
- *
- * If the session isn't initialized, this function will initialize
- * it and connect to Spotify.
- *
- * @return a pointer to the despotify session, or NULL if it can't
- * be initialized (e.g., if the configuration isn't supplied)
- */
-struct despotify_session *mpd_despotify_get_session(void);
-
-/**
- * Create a MPD tags structure from a spotify track
- *
- * @param track the track to convert
- *
- * @return a pointer to the filled in tags structure
- */
-struct tag *mpd_despotify_tag_from_track(struct ds_track *track);
-
-/**
- * Register a despotify callback.
- *
- * Despotify calls this e.g., when a track ends.
- *
- * @param cb the callback
- * @param cb_data the data to pass to the callback
- *
- * @return true if the callback could be registered
- */
-bool mpd_despotify_register_callback(void (*cb)(struct despotify_session *, int, void *, void *),
- void *cb_data);
-
-/**
- * Unregister a despotify callback.
- *
- * @param cb the callback to unregister.
- */
-void mpd_despotify_unregister_callback(void (*cb)(struct despotify_session *, int, void *, void *));
-
-#endif
-
diff --git a/src/directory.c b/src/directory.c
deleted file mode 100644
index e886698d6..000000000
--- a/src/directory.c
+++ /dev/null
@@ -1,314 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "directory.h"
-#include "song.h"
-#include "song_sort.h"
-#include "playlist_vector.h"
-#include "path.h"
-#include "util/list_sort.h"
-#include "db_visitor.h"
-#include "db_lock.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-#include <stdlib.h>
-
-struct directory *
-directory_new(const char *path, struct directory *parent)
-{
- struct directory *directory;
- size_t pathlen = strlen(path);
-
- assert(path != NULL);
- assert((*path == 0) == (parent == NULL));
-
- directory = g_malloc0(sizeof(*directory) -
- sizeof(directory->path) + pathlen + 1);
- INIT_LIST_HEAD(&directory->children);
- INIT_LIST_HEAD(&directory->songs);
- INIT_LIST_HEAD(&directory->playlists);
-
- directory->parent = parent;
- memcpy(directory->path, path, pathlen + 1);
-
- return directory;
-}
-
-void
-directory_free(struct directory *directory)
-{
- playlist_vector_deinit(&directory->playlists);
-
- struct song *song, *ns;
- directory_for_each_song_safe(song, ns, directory)
- song_free(song);
-
- struct directory *child, *n;
- directory_for_each_child_safe(child, n, directory)
- directory_free(child);
-
- g_free(directory);
- /* this resets last dir returned */
- /*directory_get_path(NULL); */
-}
-
-void
-directory_delete(struct directory *directory)
-{
- assert(holding_db_lock());
- assert(directory != NULL);
- assert(directory->parent != NULL);
-
- list_del(&directory->siblings);
- directory_free(directory);
-}
-
-const char *
-directory_get_name(const struct directory *directory)
-{
- assert(!directory_is_root(directory));
- assert(directory->path != NULL);
-
- const char *slash = strrchr(directory->path, '/');
- assert((slash == NULL) == directory_is_root(directory->parent));
-
- return slash != NULL
- ? slash + 1
- : directory->path;
-}
-
-struct directory *
-directory_new_child(struct directory *parent, const char *name_utf8)
-{
- assert(holding_db_lock());
- assert(parent != NULL);
- assert(name_utf8 != NULL);
- assert(*name_utf8 != 0);
-
- char *allocated;
- const char *path_utf8;
- if (directory_is_root(parent)) {
- allocated = NULL;
- path_utf8 = name_utf8;
- } else {
- allocated = g_strconcat(directory_get_path(parent),
- "/", name_utf8, NULL);
- path_utf8 = allocated;
- }
-
- struct directory *directory = directory_new(path_utf8, parent);
- g_free(allocated);
-
- list_add_tail(&directory->siblings, &parent->children);
- return directory;
-}
-
-struct directory *
-directory_get_child(const struct directory *directory, const char *name)
-{
- assert(holding_db_lock());
-
- struct directory *child;
- directory_for_each_child(child, directory)
- if (strcmp(directory_get_name(child), name) == 0)
- return child;
-
- return NULL;
-}
-
-void
-directory_prune_empty(struct directory *directory)
-{
- assert(holding_db_lock());
-
- struct directory *child, *n;
- directory_for_each_child_safe(child, n, directory) {
- directory_prune_empty(child);
-
- if (directory_is_empty(child))
- directory_delete(child);
- }
-}
-
-struct directory *
-directory_lookup_directory(struct directory *directory, const char *uri)
-{
- assert(holding_db_lock());
- assert(uri != NULL);
-
- if (isRootDirectory(uri))
- return directory;
-
- char *duplicated = g_strdup(uri), *name = duplicated;
-
- while (1) {
- char *slash = strchr(name, '/');
- if (slash == name) {
- directory = NULL;
- break;
- }
-
- if (slash != NULL)
- *slash = '\0';
-
- directory = directory_get_child(directory, name);
- if (directory == NULL || slash == NULL)
- break;
-
- name = slash + 1;
- }
-
- g_free(duplicated);
-
- return directory;
-}
-
-void
-directory_add_song(struct directory *directory, struct song *song)
-{
- assert(holding_db_lock());
- assert(directory != NULL);
- assert(song != NULL);
- assert(song->parent == directory);
-
- list_add_tail(&song->siblings, &directory->songs);
-}
-
-void
-directory_remove_song(G_GNUC_UNUSED struct directory *directory,
- struct song *song)
-{
- assert(holding_db_lock());
- assert(directory != NULL);
- assert(song != NULL);
- assert(song->parent == directory);
-
- list_del(&song->siblings);
-}
-
-struct song *
-directory_get_song(const struct directory *directory, const char *name_utf8)
-{
- assert(holding_db_lock());
- assert(directory != NULL);
- assert(name_utf8 != NULL);
-
- struct song *song;
- directory_for_each_song(song, directory) {
- assert(song->parent == directory);
-
- if (strcmp(song->uri, name_utf8) == 0)
- return song;
- }
-
- return NULL;
-}
-
-struct song *
-directory_lookup_song(struct directory *directory, const char *uri)
-{
- char *duplicated, *base;
-
- assert(holding_db_lock());
- assert(directory != NULL);
- assert(uri != NULL);
-
- duplicated = g_strdup(uri);
- base = strrchr(duplicated, '/');
-
- if (base != NULL) {
- *base++ = 0;
- directory = directory_lookup_directory(directory, duplicated);
- if (directory == NULL) {
- g_free(duplicated);
- return NULL;
- }
- } else
- base = duplicated;
-
- struct song *song = directory_get_song(directory, base);
- assert(song == NULL || song->parent == directory);
-
- g_free(duplicated);
- return song;
-
-}
-
-static int
-directory_cmp(G_GNUC_UNUSED void *priv,
- struct list_head *_a, struct list_head *_b)
-{
- const struct directory *a = (const struct directory *)_a;
- const struct directory *b = (const struct directory *)_b;
- return g_utf8_collate(a->path, b->path);
-}
-
-void
-directory_sort(struct directory *directory)
-{
- assert(holding_db_lock());
-
- list_sort(NULL, &directory->children, directory_cmp);
- song_list_sort(&directory->songs);
-
- struct directory *child;
- directory_for_each_child(child, directory)
- directory_sort(child);
-}
-
-bool
-directory_walk(const struct directory *directory, bool recursive,
- const struct db_visitor *visitor, void *ctx,
- GError **error_r)
-{
- assert(directory != NULL);
- assert(visitor != NULL);
- assert(error_r == NULL || *error_r == NULL);
-
- if (visitor->song != NULL) {
- struct song *song;
- directory_for_each_song(song, directory)
- if (!visitor->song(song, ctx, error_r))
- return false;
- }
-
- if (visitor->playlist != NULL) {
- struct playlist_metadata *i;
- directory_for_each_playlist(i, directory)
- if (!visitor->playlist(i, directory, ctx, error_r))
- return false;
- }
-
- struct directory *child;
- directory_for_each_child(child, directory) {
- if (visitor->directory != NULL &&
- !visitor->directory(child, ctx, error_r))
- return false;
-
- if (recursive &&
- !directory_walk(child, recursive, visitor, ctx, error_r))
- return false;
- }
-
- return true;
-}
diff --git a/src/directory.h b/src/directory.h
deleted file mode 100644
index b3cd9c8c9..000000000
--- a/src/directory.h
+++ /dev/null
@@ -1,262 +0,0 @@
-/*
- * 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_DIRECTORY_H
-#define MPD_DIRECTORY_H
-
-#include "check.h"
-#include "util/list.h"
-
-#include <glib.h>
-#include <stdbool.h>
-#include <sys/types.h>
-
-#define DEVICE_INARCHIVE (dev_t)(-1)
-#define DEVICE_CONTAINER (dev_t)(-2)
-
-#define directory_for_each_child(pos, directory) \
- list_for_each_entry(pos, &directory->children, siblings)
-
-#define directory_for_each_child_safe(pos, n, directory) \
- list_for_each_entry_safe(pos, n, &directory->children, siblings)
-
-#define directory_for_each_song(pos, directory) \
- list_for_each_entry(pos, &directory->songs, siblings)
-
-#define directory_for_each_song_safe(pos, n, directory) \
- list_for_each_entry_safe(pos, n, &directory->songs, siblings)
-
-#define directory_for_each_playlist(pos, directory) \
- list_for_each_entry(pos, &directory->playlists, siblings)
-
-#define directory_for_each_playlist_safe(pos, n, directory) \
- list_for_each_entry_safe(pos, n, &directory->playlists, siblings)
-
-struct song;
-struct db_visitor;
-
-struct directory {
- /**
- * Pointers to the siblings of this directory within the
- * parent directory. It is unused (undefined) in the root
- * directory.
- *
- * This attribute is protected with the global #db_mutex.
- * Read access in the update thread does not need protection.
- */
- struct list_head siblings;
-
- /**
- * A doubly linked list of child directories.
- *
- * This attribute is protected with the global #db_mutex.
- * Read access in the update thread does not need protection.
- */
- struct list_head children;
-
- /**
- * A doubly linked list of songs within this directory.
- *
- * This attribute is protected with the global #db_mutex.
- * Read access in the update thread does not need protection.
- */
- struct list_head songs;
-
- struct list_head playlists;
-
- struct directory *parent;
- time_t mtime;
- ino_t inode;
- dev_t device;
- bool have_stat; /* not needed if ino_t == dev_t == 0 is impossible */
- char path[sizeof(long)];
-};
-
-static inline bool
-isRootDirectory(const char *name)
-{
- return name[0] == 0 || (name[0] == '/' && name[1] == 0);
-}
-
-/**
- * Generic constructor for #directory object.
- */
-G_GNUC_MALLOC
-struct directory *
-directory_new(const char *dirname, struct directory *parent);
-
-/**
- * Create a new root #directory object.
- */
-G_GNUC_MALLOC
-static inline struct directory *
-directory_new_root(void)
-{
- return directory_new("", NULL);
-}
-
-/**
- * Free this #directory object (and the whole object tree within it),
- * assuming it was already removed from the parent.
- */
-void
-directory_free(struct directory *directory);
-
-/**
- * Remove this #directory object from its parent and free it. This
- * must not be called with the root directory.
- *
- * Caller must lock the #db_mutex.
- */
-void
-directory_delete(struct directory *directory);
-
-static inline bool
-directory_is_empty(const struct directory *directory)
-{
- return list_empty(&directory->children) &&
- list_empty(&directory->songs) &&
- list_empty(&directory->playlists);
-}
-
-static inline const char *
-directory_get_path(const struct directory *directory)
-{
- return directory->path;
-}
-
-/**
- * Is this the root directory of the music database?
- */
-static inline bool
-directory_is_root(const struct directory *directory)
-{
- return directory->parent == NULL;
-}
-
-/**
- * Returns the base name of the directory.
- */
-G_GNUC_PURE
-const char *
-directory_get_name(const struct directory *directory);
-
-/**
- * Caller must lock the #db_mutex.
- */
-G_GNUC_PURE
-struct directory *
-directory_get_child(const struct directory *directory, const char *name);
-
-/**
- * Create a new #directory object as a child of the given one.
- *
- * Caller must lock the #db_mutex.
- *
- * @param parent the parent directory the new one will be added to
- * @param name_utf8 the UTF-8 encoded name of the new sub directory
- */
-G_GNUC_MALLOC
-struct directory *
-directory_new_child(struct directory *parent, const char *name_utf8);
-
-/**
- * Look up a sub directory, and create the object if it does not
- * exist.
- *
- * Caller must lock the #db_mutex.
- */
-static inline struct directory *
-directory_make_child(struct directory *directory, const char *name_utf8)
-{
- struct directory *child = directory_get_child(directory, name_utf8);
- if (child == NULL)
- child = directory_new_child(directory, name_utf8);
- return child;
-}
-
-/**
- * Caller must lock the #db_mutex.
- */
-void
-directory_prune_empty(struct directory *directory);
-
-/**
- * Looks up a directory by its relative URI.
- *
- * @param directory the parent (or grandparent, ...) directory
- * @param uri the relative URI
- * @return the directory, or NULL if none was found
- */
-struct directory *
-directory_lookup_directory(struct directory *directory, const char *uri);
-
-/**
- * Add a song object to this directory. Its "parent" attribute must
- * be set already.
- */
-void
-directory_add_song(struct directory *directory, struct song *song);
-
-/**
- * Remove a song object from this directory (which effectively
- * invalidates the song object, because the "parent" attribute becomes
- * stale), but does not free it.
- */
-void
-directory_remove_song(struct directory *directory, struct song *song);
-
-/**
- * Look up a song in this directory by its name.
- *
- * Caller must lock the #db_mutex.
- */
-G_GNUC_PURE
-struct song *
-directory_get_song(const struct directory *directory, const char *name_utf8);
-
-/**
- * Looks up a song by its relative URI.
- *
- * Caller must lock the #db_mutex.
- *
- * @param directory the parent (or grandparent, ...) directory
- * @param uri the relative URI
- * @return the song, or NULL if none was found
- */
-struct song *
-directory_lookup_song(struct directory *directory, const char *uri);
-
-/**
- * Sort all directory entries recursively.
- *
- * Caller must lock the #db_mutex.
- */
-void
-directory_sort(struct directory *directory);
-
-/**
- * Caller must lock #db_mutex.
- */
-bool
-directory_walk(const struct directory *directory, bool recursive,
- const struct db_visitor *visitor, void *ctx,
- GError **error_r);
-
-#endif
diff --git a/src/directory_save.c b/src/directory_save.c
deleted file mode 100644
index de1df050a..000000000
--- a/src/directory_save.c
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "directory_save.h"
-#include "directory.h"
-#include "song.h"
-#include "text_file.h"
-#include "song_save.h"
-#include "playlist_database.h"
-
-#include <assert.h>
-#include <string.h>
-
-#define DIRECTORY_DIR "directory: "
-#define DIRECTORY_MTIME "mtime: "
-#define DIRECTORY_BEGIN "begin: "
-#define DIRECTORY_END "end: "
-
-/**
- * The quark used for GError.domain.
- */
-static inline GQuark
-directory_quark(void)
-{
- return g_quark_from_static_string("directory");
-}
-
-void
-directory_save(FILE *fp, const struct directory *directory)
-{
- if (!directory_is_root(directory)) {
- fprintf(fp, DIRECTORY_MTIME "%lu\n",
- (unsigned long)directory->mtime);
-
- fprintf(fp, "%s%s\n", DIRECTORY_BEGIN,
- directory_get_path(directory));
- }
-
- struct directory *cur;
- directory_for_each_child(cur, directory) {
- char *base = g_path_get_basename(cur->path);
-
- fprintf(fp, DIRECTORY_DIR "%s\n", base);
- g_free(base);
-
- directory_save(fp, cur);
-
- if (ferror(fp))
- return;
- }
-
- struct song *song;
- directory_for_each_song(song, directory)
- song_save(fp, song);
-
- playlist_vector_save(fp, &directory->playlists);
-
- if (!directory_is_root(directory))
- fprintf(fp, DIRECTORY_END "%s\n",
- directory_get_path(directory));
-}
-
-static struct directory *
-directory_load_subdir(FILE *fp, struct directory *parent, const char *name,
- GString *buffer, GError **error_r)
-{
- const char *line;
- bool success;
-
- if (directory_get_child(parent, name) != NULL) {
- g_set_error(error_r, directory_quark(), 0,
- "Duplicate subdirectory '%s'", name);
- return NULL;
- }
-
- struct directory *directory = directory_new_child(parent, name);
-
- line = read_text_line(fp, buffer);
- if (line == NULL) {
- g_set_error(error_r, directory_quark(), 0,
- "Unexpected end of file");
- directory_delete(directory);
- return NULL;
- }
-
- if (g_str_has_prefix(line, DIRECTORY_MTIME)) {
- directory->mtime =
- g_ascii_strtoull(line + sizeof(DIRECTORY_MTIME) - 1,
- NULL, 10);
-
- line = read_text_line(fp, buffer);
- if (line == NULL) {
- g_set_error(error_r, directory_quark(), 0,
- "Unexpected end of file");
- directory_delete(directory);
- return NULL;
- }
- }
-
- if (!g_str_has_prefix(line, DIRECTORY_BEGIN)) {
- g_set_error(error_r, directory_quark(), 0,
- "Malformed line: %s", line);
- directory_delete(directory);
- return NULL;
- }
-
- success = directory_load(fp, directory, buffer, error_r);
- if (!success) {
- directory_delete(directory);
- return NULL;
- }
-
- return directory;
-}
-
-bool
-directory_load(FILE *fp, struct directory *directory,
- GString *buffer, GError **error)
-{
- const char *line;
-
- while ((line = read_text_line(fp, buffer)) != NULL &&
- !g_str_has_prefix(line, DIRECTORY_END)) {
- if (g_str_has_prefix(line, DIRECTORY_DIR)) {
- struct directory *subdir =
- directory_load_subdir(fp, directory,
- line + sizeof(DIRECTORY_DIR) - 1,
- buffer, error);
- if (subdir == NULL)
- return false;
- } else if (g_str_has_prefix(line, SONG_BEGIN)) {
- const char *name = line + sizeof(SONG_BEGIN) - 1;
- struct song *song;
-
- if (directory_get_song(directory, name) != NULL) {
- g_set_error(error, directory_quark(), 0,
- "Duplicate song '%s'", name);
- return NULL;
- }
-
- song = song_load(fp, directory, name,
- buffer, error);
- if (song == NULL)
- return false;
-
- directory_add_song(directory, 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);
-
- if (!playlist_metadata_load(fp, &directory->playlists,
- name, buffer, error)) {
- g_free(name);
- return false;
- }
-
- g_free(name);
- } else {
- g_set_error(error, directory_quark(), 0,
- "Malformed line: %s", line);
- return false;
- }
- }
-
- return true;
-}
diff --git a/src/directory_save.h b/src/directory_save.h
deleted file mode 100644
index 2d0056830..000000000
--- a/src/directory_save.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * 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_DIRECTORY_SAVE_H
-#define MPD_DIRECTORY_SAVE_H
-
-#include <glib.h>
-
-#include <stdbool.h>
-#include <stdio.h>
-
-struct directory;
-
-void
-directory_save(FILE *fp, const struct directory *directory);
-
-bool
-directory_load(FILE *fp, struct directory *directory,
- GString *buffer, GError **error);
-
-#endif
diff --git a/src/dsd2pcm/dsd2pcm.hpp b/src/dsd2pcm/dsd2pcm.hpp
deleted file mode 100644
index b1b2ae1c5..000000000
--- a/src/dsd2pcm/dsd2pcm.hpp
+++ /dev/null
@@ -1,41 +0,0 @@
-#ifndef DSD2PCM_HXX_INCLUDED
-#define DSD2PCM_HXX_INCLUDED
-
-#include <algorithm>
-#include <stdexcept>
-#include "dsd2pcm.h"
-
-/**
- * C++ PImpl Wrapper for the dsd2pcm C library
- */
-
-class dxd
-{
- dsd2pcm_ctx *handle;
-public:
- dxd() : handle(dsd2pcm_init())
- { if (!handle) throw std::runtime_error("wtf?!"); }
-
- dxd(dxd const& x) : handle(dsd2pcm_clone(x.handle))
- { if (!handle) throw std::runtime_error("wtf?!"); }
-
- ~dxd() { dsd2pcm_destroy(handle); }
-
- friend void swap(dxd & a, dxd & b)
- { std::swap(a.handle,b.handle); }
-
- dxd& operator=(dxd x)
- { swap(*this,x); return *this; }
-
- void translate(size_t samples,
- const unsigned char *src, ptrdiff_t src_stride,
- bool lsbitfirst,
- float *dst, ptrdiff_t dst_stride)
- {
- dsd2pcm_translate(handle,samples,src,src_stride,
- lsbitfirst,dst,dst_stride);
- }
-};
-
-#endif // DSD2PCM_HXX_INCLUDED
-
diff --git a/src/dsd2pcm/noiseshape.hpp b/src/dsd2pcm/noiseshape.hpp
deleted file mode 100644
index 726272f91..000000000
--- a/src/dsd2pcm/noiseshape.hpp
+++ /dev/null
@@ -1,46 +0,0 @@
-#ifndef NOISE_SHAPE_HXX_INCLUDED
-#define NOISE_SHAPE_HXX_INCLUDED
-
-#include <stdexcept>
-#include "noiseshape.h"
-
-/**
- * C++ wrapper for the noiseshape C library
- */
-
-class noise_shaper
-{
- noise_shape_ctx ctx;
-public:
- noise_shaper(int sos_count, const float *bbaa)
- {
- if (noise_shape_init(&ctx,sos_count,bbaa))
- throw std::runtime_error("noise shaper initialization failed");
- }
-
- noise_shaper(noise_shaper const& x)
- {
- if (noise_shape_clone(&x.ctx,&ctx))
- throw std::runtime_error("noise shaper initialization failed");
- }
-
- ~noise_shaper()
- { noise_shape_destroy(&ctx); }
-
- noise_shaper& operator=(noise_shaper const& x)
- {
- if (this != &x) {
- noise_shape_destroy(&ctx);
- if (noise_shape_clone(&x.ctx,&ctx))
- throw std::runtime_error("noise shaper initialization failed");
- }
- return *this;
- }
-
- float get() { return noise_shape_get(&ctx); }
-
- void update(float error) { noise_shape_update(&ctx,error); }
-};
-
-#endif /* NOISE_SHAPE_HXX_INCLUDED */
-
diff --git a/src/dummy.cxx b/src/dummy.cxx
deleted file mode 100644
index b21555d06..000000000
--- a/src/dummy.cxx
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * Just a dummy C++ file that is linked to work around an automake
- * bug: automake uses CXXLD when at least one source is C++, but when
- * you link a static library with a C++ source, it uses CCLD. This
- * causes linker problems (undefined reference to 'operator
- * delete(void*)'), because CCLD does not link with libstdc++.
- *
- * By linking with this empty C++ source, automake decides to use
- * CXXLD.
- *
- */
diff --git a/src/encoder/FlacEncoderPlugin.cxx b/src/encoder/FlacEncoderPlugin.cxx
new file mode 100644
index 000000000..a14b97f11
--- /dev/null
+++ b/src/encoder/FlacEncoderPlugin.cxx
@@ -0,0 +1,346 @@
+/*
+ * 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 "FlacEncoderPlugin.hxx"
+#include "EncoderAPI.hxx"
+#include "AudioFormat.hxx"
+#include "pcm/PcmBuffer.hxx"
+#include "util/fifo_buffer.h"
+
+extern "C" {
+#include "util/growing_fifo.h"
+}
+
+#include <assert.h>
+#include <string.h>
+
+#include <FLAC/stream_encoder.h>
+
+#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7
+#error libFLAC is too old
+#endif
+
+struct flac_encoder {
+ Encoder encoder;
+
+ AudioFormat audio_format;
+ unsigned compression;
+
+ FLAC__StreamEncoder *fse;
+
+ PcmBuffer expand_buffer;
+
+ /**
+ * This buffer will hold encoded data from libFLAC until it is
+ * picked up with flac_encoder_read().
+ */
+ struct fifo_buffer *output_buffer;
+
+ flac_encoder():encoder(flac_encoder_plugin) {}
+};
+
+static inline GQuark
+flac_encoder_quark(void)
+{
+ return g_quark_from_static_string("flac_encoder");
+}
+
+static bool
+flac_encoder_configure(struct flac_encoder *encoder, const config_param &param,
+ gcc_unused GError **error)
+{
+ encoder->compression = param.GetBlockValue("compression", 5u);
+
+ return true;
+}
+
+static Encoder *
+flac_encoder_init(const config_param &param, GError **error)
+{
+ flac_encoder *encoder = new flac_encoder();
+
+ /* load configuration from "param" */
+ if (!flac_encoder_configure(encoder, param, error)) {
+ /* configuration has failed, roll back and return error */
+ delete encoder;
+ return nullptr;
+ }
+
+ return &encoder->encoder;
+}
+
+static void
+flac_encoder_finish(Encoder *_encoder)
+{
+ struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
+
+ /* the real libFLAC cleanup was already performed by
+ flac_encoder_close(), so no real work here */
+ delete encoder;
+}
+
+static bool
+flac_encoder_setup(struct flac_encoder *encoder, unsigned bits_per_sample,
+ GError **error)
+{
+ if ( !FLAC__stream_encoder_set_compression_level(encoder->fse,
+ encoder->compression)) {
+ g_set_error(error, flac_encoder_quark(), 0,
+ "error setting flac compression to %d",
+ encoder->compression);
+ return false;
+ }
+
+ if ( !FLAC__stream_encoder_set_channels(encoder->fse,
+ encoder->audio_format.channels)) {
+ g_set_error(error, flac_encoder_quark(), 0,
+ "error setting flac channels num to %d",
+ encoder->audio_format.channels);
+ return false;
+ }
+ if ( !FLAC__stream_encoder_set_bits_per_sample(encoder->fse,
+ bits_per_sample)) {
+ g_set_error(error, flac_encoder_quark(), 0,
+ "error setting flac bit format to %d",
+ bits_per_sample);
+ return false;
+ }
+ if ( !FLAC__stream_encoder_set_sample_rate(encoder->fse,
+ encoder->audio_format.sample_rate)) {
+ g_set_error(error, flac_encoder_quark(), 0,
+ "error setting flac sample rate to %d",
+ encoder->audio_format.sample_rate);
+ return false;
+ }
+ return true;
+}
+
+static FLAC__StreamEncoderWriteStatus
+flac_write_callback(G_GNUC_UNUSED const FLAC__StreamEncoder *fse,
+ const FLAC__byte data[],
+ size_t bytes,
+ G_GNUC_UNUSED unsigned samples,
+ G_GNUC_UNUSED unsigned current_frame, void *client_data)
+{
+ struct flac_encoder *encoder = (struct flac_encoder *) client_data;
+
+ //transfer data to buffer
+ growing_fifo_append(&encoder->output_buffer, data, bytes);
+
+ return FLAC__STREAM_ENCODER_WRITE_STATUS_OK;
+}
+
+static void
+flac_encoder_close(Encoder *_encoder)
+{
+ struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
+
+ FLAC__stream_encoder_delete(encoder->fse);
+
+ encoder->expand_buffer.Clear();
+ fifo_buffer_free(encoder->output_buffer);
+}
+
+static bool
+flac_encoder_open(Encoder *_encoder, AudioFormat &audio_format,
+ GError **error)
+{
+ struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
+ unsigned bits_per_sample;
+
+ encoder->audio_format = audio_format;
+
+ /* FIXME: flac should support 32bit as well */
+ switch (audio_format.format) {
+ case SampleFormat::S8:
+ bits_per_sample = 8;
+ break;
+
+ case SampleFormat::S16:
+ bits_per_sample = 16;
+ break;
+
+ case SampleFormat::S24_P32:
+ bits_per_sample = 24;
+ break;
+
+ default:
+ bits_per_sample = 24;
+ audio_format.format = SampleFormat::S24_P32;
+ }
+
+ /* allocate the encoder */
+ encoder->fse = FLAC__stream_encoder_new();
+ if (encoder->fse == nullptr) {
+ g_set_error(error, flac_encoder_quark(), 0,
+ "flac_new() failed");
+ return false;
+ }
+
+ if (!flac_encoder_setup(encoder, bits_per_sample, error)) {
+ FLAC__stream_encoder_delete(encoder->fse);
+ return false;
+ }
+
+ encoder->output_buffer = growing_fifo_new();
+
+ /* this immediately outputs data through callback */
+
+ {
+ FLAC__StreamEncoderInitStatus init_status;
+
+ init_status = FLAC__stream_encoder_init_stream(encoder->fse,
+ flac_write_callback,
+ nullptr, nullptr, nullptr, encoder);
+
+ if(init_status != FLAC__STREAM_ENCODER_INIT_STATUS_OK) {
+ g_set_error(error, flac_encoder_quark(), 0,
+ "failed to initialize encoder: %s\n",
+ FLAC__StreamEncoderInitStatusString[init_status]);
+ flac_encoder_close(_encoder);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+static bool
+flac_encoder_flush(Encoder *_encoder, G_GNUC_UNUSED GError **error)
+{
+ struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
+
+ (void) FLAC__stream_encoder_finish(encoder->fse);
+ return true;
+}
+
+static inline void
+pcm8_to_flac(int32_t *out, const int8_t *in, unsigned num_samples)
+{
+ while (num_samples > 0) {
+ *out++ = *in++;
+ --num_samples;
+ }
+}
+
+static inline void
+pcm16_to_flac(int32_t *out, const int16_t *in, unsigned num_samples)
+{
+ while (num_samples > 0) {
+ *out++ = *in++;
+ --num_samples;
+ }
+}
+
+static bool
+flac_encoder_write(Encoder *_encoder,
+ const void *data, size_t length,
+ G_GNUC_UNUSED GError **error)
+{
+ struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
+ unsigned num_frames, num_samples;
+ void *exbuffer;
+ const void *buffer = nullptr;
+
+ /* format conversion */
+
+ num_frames = length / encoder->audio_format.GetFrameSize();
+ num_samples = num_frames * encoder->audio_format.channels;
+
+ switch (encoder->audio_format.format) {
+ case SampleFormat::S8:
+ exbuffer = encoder->expand_buffer.Get(length * 4);
+ pcm8_to_flac((int32_t *)exbuffer, (const int8_t *)data,
+ num_samples);
+ buffer = exbuffer;
+ break;
+
+ case SampleFormat::S16:
+ exbuffer = encoder->expand_buffer.Get(length * 2);
+ pcm16_to_flac((int32_t *)exbuffer, (const int16_t *)data,
+ num_samples);
+ buffer = exbuffer;
+ break;
+
+ case SampleFormat::S24_P32:
+ case SampleFormat::S32:
+ /* nothing need to be done; format is the same for
+ both mpd and libFLAC */
+ buffer = data;
+ break;
+
+ default:
+ gcc_unreachable();
+ }
+
+ /* feed samples to encoder */
+
+ if (!FLAC__stream_encoder_process_interleaved(encoder->fse,
+ (const FLAC__int32 *)buffer,
+ num_frames)) {
+ g_set_error(error, flac_encoder_quark(), 0,
+ "flac encoder process failed");
+ return false;
+ }
+
+ return true;
+}
+
+static size_t
+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;
+}
+
+static const char *
+flac_encoder_get_mime_type(G_GNUC_UNUSED Encoder *_encoder)
+{
+ return "audio/flac";
+}
+
+const EncoderPlugin flac_encoder_plugin = {
+ "flac",
+ flac_encoder_init,
+ flac_encoder_finish,
+ flac_encoder_open,
+ flac_encoder_close,
+ flac_encoder_flush,
+ flac_encoder_flush,
+ nullptr,
+ nullptr,
+ flac_encoder_write,
+ flac_encoder_read,
+ flac_encoder_get_mime_type,
+};
+
diff --git a/src/encoder/FlacEncoderPlugin.hxx b/src/encoder/FlacEncoderPlugin.hxx
new file mode 100644
index 000000000..928a7f93e
--- /dev/null
+++ b/src/encoder/FlacEncoderPlugin.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_ENCODER_FLAC_HXX
+#define MPD_ENCODER_FLAC_HXX
+
+extern const struct EncoderPlugin flac_encoder_plugin;
+
+#endif
diff --git a/src/encoder/LameEncoderPlugin.cxx b/src/encoder/LameEncoderPlugin.cxx
new file mode 100644
index 000000000..db93b93a3
--- /dev/null
+++ b/src/encoder/LameEncoderPlugin.cxx
@@ -0,0 +1,300 @@
+/*
+ * 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 "LameEncoderPlugin.hxx"
+#include "EncoderAPI.hxx"
+#include "AudioFormat.hxx"
+
+#include <lame/lame.h>
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+
+struct LameEncoder final {
+ Encoder encoder;
+
+ AudioFormat audio_format;
+ float quality;
+ int bitrate;
+
+ lame_global_flags *gfp;
+
+ unsigned char buffer[32768];
+ size_t buffer_length;
+
+ LameEncoder():encoder(lame_encoder_plugin) {}
+
+ bool Configure(const config_param &param, GError **error);
+};
+
+static inline GQuark
+lame_encoder_quark(void)
+{
+ return g_quark_from_static_string("lame_encoder");
+}
+
+bool
+LameEncoder::Configure(const config_param &param, GError **error)
+{
+ const char *value;
+ char *endptr;
+
+ value = param.GetBlockValue("quality");
+ if (value != nullptr) {
+ /* a quality was configured (VBR) */
+
+ quality = g_ascii_strtod(value, &endptr);
+
+ if (*endptr != '\0' || quality < -1.0 || quality > 10.0) {
+ g_set_error(error, lame_encoder_quark(), 0,
+ "quality \"%s\" is not a number in the "
+ "range -1 to 10, line %i",
+ value, param.line);
+ return false;
+ }
+
+ if (param.GetBlockValue("bitrate") != nullptr) {
+ g_set_error(error, lame_encoder_quark(), 0,
+ "quality and bitrate are "
+ "both defined (line %i)",
+ param.line);
+ return false;
+ }
+ } else {
+ /* a bit rate was configured */
+
+ value = param.GetBlockValue("bitrate");
+ if (value == nullptr) {
+ g_set_error(error, lame_encoder_quark(), 0,
+ "neither bitrate nor quality defined "
+ "at line %i",
+ param.line);
+ return false;
+ }
+
+ quality = -2.0;
+ bitrate = g_ascii_strtoll(value, &endptr, 10);
+
+ if (*endptr != '\0' || bitrate <= 0) {
+ g_set_error(error, lame_encoder_quark(), 0,
+ "bitrate at line %i should be a positive integer",
+ param.line);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static Encoder *
+lame_encoder_init(const config_param &param, GError **error_r)
+{
+ LameEncoder *encoder = new LameEncoder();
+
+ /* load configuration from "param" */
+ if (!encoder->Configure(param, error_r)) {
+ /* configuration has failed, roll back and return error */
+ delete encoder;
+ return nullptr;
+ }
+
+ return &encoder->encoder;
+}
+
+static void
+lame_encoder_finish(Encoder *_encoder)
+{
+ LameEncoder *encoder = (LameEncoder *)_encoder;
+
+ /* the real liblame cleanup was already performed by
+ lame_encoder_close(), so no real work here */
+ g_free(encoder);
+}
+
+static bool
+lame_encoder_setup(LameEncoder *encoder, GError **error)
+{
+ if (encoder->quality >= -1.0) {
+ /* a quality was configured (VBR) */
+
+ if (0 != lame_set_VBR(encoder->gfp, vbr_rh)) {
+ g_set_error(error, lame_encoder_quark(), 0,
+ "error setting lame VBR mode");
+ return false;
+ }
+ if (0 != lame_set_VBR_q(encoder->gfp, encoder->quality)) {
+ g_set_error(error, lame_encoder_quark(), 0,
+ "error setting lame VBR quality");
+ return false;
+ }
+ } else {
+ /* a bit rate was configured */
+
+ if (0 != lame_set_brate(encoder->gfp, encoder->bitrate)) {
+ g_set_error(error, lame_encoder_quark(), 0,
+ "error setting lame bitrate");
+ return false;
+ }
+ }
+
+ if (0 != lame_set_num_channels(encoder->gfp,
+ encoder->audio_format.channels)) {
+ g_set_error(error, lame_encoder_quark(), 0,
+ "error setting lame num channels");
+ return false;
+ }
+
+ if (0 != lame_set_in_samplerate(encoder->gfp,
+ encoder->audio_format.sample_rate)) {
+ g_set_error(error, lame_encoder_quark(), 0,
+ "error setting lame sample rate");
+ return false;
+ }
+
+ if (0 != lame_set_out_samplerate(encoder->gfp,
+ encoder->audio_format.sample_rate)) {
+ g_set_error(error, lame_encoder_quark(), 0,
+ "error setting lame out sample rate");
+ return false;
+ }
+
+ if (0 > lame_init_params(encoder->gfp)) {
+ g_set_error(error, lame_encoder_quark(), 0,
+ "error initializing lame params");
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+lame_encoder_open(Encoder *_encoder, AudioFormat &audio_format,
+ GError **error)
+{
+ LameEncoder *encoder = (LameEncoder *)_encoder;
+
+ audio_format.format = SampleFormat::S16;
+ audio_format.channels = 2;
+
+ encoder->audio_format = audio_format;
+
+ encoder->gfp = lame_init();
+ if (encoder->gfp == nullptr) {
+ g_set_error(error, lame_encoder_quark(), 0,
+ "lame_init() failed");
+ return false;
+ }
+
+ if (!lame_encoder_setup(encoder, error)) {
+ lame_close(encoder->gfp);
+ return false;
+ }
+
+ encoder->buffer_length = 0;
+
+ return true;
+}
+
+static void
+lame_encoder_close(Encoder *_encoder)
+{
+ LameEncoder *encoder = (LameEncoder *)_encoder;
+
+ lame_close(encoder->gfp);
+}
+
+static bool
+lame_encoder_write(Encoder *_encoder,
+ const void *data, size_t length,
+ gcc_unused GError **error)
+{
+ LameEncoder *encoder = (LameEncoder *)_encoder;
+ const int16_t *src = (const int16_t*)data;
+
+ assert(encoder->buffer_length == 0);
+
+ const unsigned num_frames =
+ length / encoder->audio_format.GetFrameSize();
+ float *left = g_new(float, num_frames);
+ float *right = g_new(float, num_frames);
+
+ /* this is for only 16-bit audio */
+
+ for (unsigned i = 0; i < num_frames; i++) {
+ left[i] = *src++;
+ right[i] = *src++;
+ }
+
+ int bytes_out = lame_encode_buffer_float(encoder->gfp, left, right,
+ num_frames, encoder->buffer,
+ sizeof(encoder->buffer));
+
+ g_free(left);
+ g_free(right);
+
+ if (bytes_out < 0) {
+ g_set_error(error, lame_encoder_quark(), 0,
+ "lame encoder failed");
+ return false;
+ }
+
+ encoder->buffer_length = (size_t)bytes_out;
+ return true;
+}
+
+static size_t
+lame_encoder_read(Encoder *_encoder, void *dest, size_t length)
+{
+ LameEncoder *encoder = (LameEncoder *)_encoder;
+
+ if (length > encoder->buffer_length)
+ length = encoder->buffer_length;
+
+ memcpy(dest, encoder->buffer, length);
+
+ encoder->buffer_length -= length;
+ memmove(encoder->buffer, encoder->buffer + length,
+ encoder->buffer_length);
+
+ return length;
+}
+
+static const char *
+lame_encoder_get_mime_type(gcc_unused Encoder *_encoder)
+{
+ return "audio/mpeg";
+}
+
+const EncoderPlugin lame_encoder_plugin = {
+ "lame",
+ lame_encoder_init,
+ lame_encoder_finish,
+ lame_encoder_open,
+ lame_encoder_close,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ lame_encoder_write,
+ lame_encoder_read,
+ lame_encoder_get_mime_type,
+};
diff --git a/src/encoder/LameEncoderPlugin.hxx b/src/encoder/LameEncoderPlugin.hxx
new file mode 100644
index 000000000..49832baee
--- /dev/null
+++ b/src/encoder/LameEncoderPlugin.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_ENCODER_LAME_HXX
+#define MPD_ENCODER_LAME_HXX
+
+extern const struct EncoderPlugin lame_encoder_plugin;
+
+#endif
diff --git a/src/encoder/NullEncoderPlugin.cxx b/src/encoder/NullEncoderPlugin.cxx
new file mode 100644
index 000000000..5c01fbd98
--- /dev/null
+++ b/src/encoder/NullEncoderPlugin.cxx
@@ -0,0 +1,119 @@
+/*
+ * 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 "NullEncoderPlugin.hxx"
+#include "EncoderAPI.hxx"
+#include "util/fifo_buffer.h"
+extern "C" {
+#include "util/growing_fifo.h"
+}
+#include "gcc.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+
+struct NullEncoder final {
+ Encoder encoder;
+
+ struct fifo_buffer *buffer;
+
+ NullEncoder():encoder(null_encoder_plugin) {}
+};
+
+static Encoder *
+null_encoder_init(gcc_unused const config_param &param,
+ gcc_unused GError **error)
+{
+ NullEncoder *encoder = new NullEncoder();
+ return &encoder->encoder;
+}
+
+static void
+null_encoder_finish(Encoder *_encoder)
+{
+ NullEncoder *encoder = (NullEncoder *)_encoder;
+
+ delete encoder;
+}
+
+static void
+null_encoder_close(Encoder *_encoder)
+{
+ NullEncoder *encoder = (NullEncoder *)_encoder;
+
+ fifo_buffer_free(encoder->buffer);
+}
+
+
+static bool
+null_encoder_open(Encoder *_encoder,
+ gcc_unused AudioFormat &audio_format,
+ gcc_unused GError **error)
+{
+ NullEncoder *encoder = (NullEncoder *)_encoder;
+ encoder->buffer = growing_fifo_new();
+ return true;
+}
+
+static bool
+null_encoder_write(Encoder *_encoder,
+ const void *data, size_t length,
+ gcc_unused GError **error)
+{
+ NullEncoder *encoder = (NullEncoder *)_encoder;
+
+ growing_fifo_append(&encoder->buffer, data, length);
+ return length;
+}
+
+static size_t
+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;
+}
+
+const EncoderPlugin null_encoder_plugin = {
+ "null",
+ null_encoder_init,
+ null_encoder_finish,
+ null_encoder_open,
+ null_encoder_close,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ null_encoder_write,
+ null_encoder_read,
+ nullptr,
+};
diff --git a/src/encoder/NullEncoderPlugin.hxx b/src/encoder/NullEncoderPlugin.hxx
new file mode 100644
index 000000000..b741a2f6d
--- /dev/null
+++ b/src/encoder/NullEncoderPlugin.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_ENCODER_NULL_HXX
+#define MPD_ENCODER_NULL_HXX
+
+extern const struct EncoderPlugin null_encoder_plugin;
+
+#endif
diff --git a/src/encoder/OggStream.hxx b/src/encoder/OggStream.hxx
new file mode 100644
index 000000000..ce847f491
--- /dev/null
+++ b/src/encoder/OggStream.hxx
@@ -0,0 +1,128 @@
+/*
+ * 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.
+ */
+
+#ifndef MPD_OGG_STREAM_HXX
+#define MPD_OGG_STREAM_HXX
+
+#include "check.h"
+
+#include <ogg/ogg.h>
+
+#include <assert.h>
+#include <string.h>
+#include <stdint.h>
+
+class OggStream {
+ ogg_stream_state state;
+
+ bool flush;
+
+#ifndef NDEBUG
+ bool initialized;
+#endif
+
+public:
+#ifndef NDEBUG
+ OggStream():initialized(false) {}
+ ~OggStream() {
+ assert(!initialized);
+ }
+#endif
+
+ void Initialize(int serialno) {
+ assert(!initialized);
+
+ ogg_stream_init(&state, serialno);
+
+ /* set "flush" to true, so the caller gets the full
+ headers on the first read() */
+ flush = true;
+
+#ifndef NDEBUG
+ initialized = true;
+#endif
+ }
+
+ void Reinitialize(int serialno) {
+ assert(initialized);
+
+ ogg_stream_reset_serialno(&state, serialno);
+
+ /* set "flush" to true, so the caller gets the full
+ headers on the first read() */
+ flush = true;
+ }
+
+ void Deinitialize() {
+ assert(initialized);
+
+ ogg_stream_clear(&state);
+
+#ifndef NDEBUG
+ initialized = false;
+#endif
+ }
+
+ void Flush() {
+ assert(initialized);
+
+ flush = true;
+ }
+
+ void PacketIn(const ogg_packet &packet) {
+ assert(initialized);
+
+ ogg_stream_packetin(&state,
+ const_cast<ogg_packet *>(&packet));
+ }
+
+ bool PageOut(ogg_page &page) {
+ int result = ogg_stream_pageout(&state, &page);
+ if (result == 0 && flush) {
+ flush = false;
+ result = ogg_stream_flush(&state, &page);
+ }
+
+ return result != 0;
+ }
+
+ size_t PageOut(void *_buffer, size_t size) {
+ ogg_page page;
+ if (!PageOut(page))
+ return 0;
+
+ assert(page.header_len > 0 || page.body_len > 0);
+
+ size_t header_len = (size_t)page.header_len;
+ size_t body_len = (size_t)page.body_len;
+ assert(header_len <= size);
+
+ if (header_len + body_len > size)
+ /* TODO: better overflow handling */
+ body_len = size - header_len;
+
+ uint8_t *buffer = (uint8_t *)_buffer;
+ memcpy(buffer, page.header, header_len);
+ memcpy(buffer + header_len, page.body, body_len);
+
+ return header_len + body_len;
+ }
+};
+
+#endif
diff --git a/src/encoder/OpusEncoderPlugin.cxx b/src/encoder/OpusEncoderPlugin.cxx
new file mode 100644
index 000000000..d67cf1862
--- /dev/null
+++ b/src/encoder/OpusEncoderPlugin.cxx
@@ -0,0 +1,425 @@
+/*
+ * 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 "OpusEncoderPlugin.hxx"
+#include "OggStream.hxx"
+#include "EncoderAPI.hxx"
+#include "AudioFormat.hxx"
+#include "mpd_error.h"
+
+#include <opus.h>
+#include <ogg/ogg.h>
+
+#include <assert.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "opus_encoder"
+
+struct opus_encoder {
+ /** the base class */
+ Encoder encoder;
+
+ /* configuration */
+
+ opus_int32 bitrate;
+ int complexity;
+ int signal;
+
+ /* runtime information */
+
+ AudioFormat audio_format;
+
+ size_t frame_size;
+
+ size_t buffer_frames, buffer_size, buffer_position;
+ uint8_t *buffer;
+
+ OpusEncoder *enc;
+
+ unsigned char buffer2[1275 * 3 + 7];
+
+ OggStream stream;
+
+ int lookahead;
+
+ ogg_int64_t packetno;
+
+ ogg_int64_t granulepos;
+
+ opus_encoder():encoder(opus_encoder_plugin) {}
+};
+
+gcc_const
+static inline GQuark
+opus_encoder_quark(void)
+{
+ return g_quark_from_static_string("opus_encoder");
+}
+
+static bool
+opus_encoder_configure(struct opus_encoder *encoder,
+ const config_param &param, GError **error_r)
+{
+ const char *value = param.GetBlockValue("bitrate", "auto");
+ if (strcmp(value, "auto") == 0)
+ encoder->bitrate = OPUS_AUTO;
+ else if (strcmp(value, "max") == 0)
+ encoder->bitrate = OPUS_BITRATE_MAX;
+ else {
+ char *endptr;
+ encoder->bitrate = strtoul(value, &endptr, 10);
+ if (endptr == value || *endptr != 0 ||
+ encoder->bitrate < 500 || encoder->bitrate > 512000) {
+ g_set_error(error_r, opus_encoder_quark(), 0,
+ "Invalid bit rate");
+ return false;
+ }
+ }
+
+ encoder->complexity = param.GetBlockValue("complexity", 10u);
+ if (encoder->complexity > 10) {
+ g_set_error(error_r, opus_encoder_quark(), 0,
+ "Invalid complexity");
+ return false;
+ }
+
+ value = param.GetBlockValue("signal", "auto");
+ if (strcmp(value, "auto") == 0)
+ encoder->signal = OPUS_AUTO;
+ else if (strcmp(value, "voice") == 0)
+ encoder->signal = OPUS_SIGNAL_VOICE;
+ else if (strcmp(value, "music") == 0)
+ encoder->signal = OPUS_SIGNAL_MUSIC;
+ else {
+ g_set_error(error_r, opus_encoder_quark(), 0,
+ "Invalid signal");
+ return false;
+ }
+
+ return true;
+}
+
+static Encoder *
+opus_encoder_init(const config_param &param, GError **error)
+{
+ opus_encoder *encoder = new opus_encoder();
+
+ /* load configuration from "param" */
+ if (!opus_encoder_configure(encoder, param, error)) {
+ /* configuration has failed, roll back and return error */
+ delete encoder;
+ return NULL;
+ }
+
+ return &encoder->encoder;
+}
+
+static void
+opus_encoder_finish(Encoder *_encoder)
+{
+ struct opus_encoder *encoder = (struct opus_encoder *)_encoder;
+
+ /* the real libopus cleanup was already performed by
+ opus_encoder_close(), so no real work here */
+ delete encoder;
+}
+
+static bool
+opus_encoder_open(Encoder *_encoder,
+ AudioFormat &audio_format,
+ GError **error_r)
+{
+ struct opus_encoder *encoder = (struct opus_encoder *)_encoder;
+
+ /* libopus supports only 48 kHz */
+ audio_format.sample_rate = 48000;
+
+ if (audio_format.channels > 2)
+ audio_format.channels = 1;
+
+ switch (audio_format.format) {
+ case SampleFormat::S16:
+ case SampleFormat::FLOAT:
+ break;
+
+ case SampleFormat::S8:
+ audio_format.format = SampleFormat::S16;
+ break;
+
+ default:
+ audio_format.format = SampleFormat::FLOAT;
+ break;
+ }
+
+ encoder->audio_format = audio_format;
+ encoder->frame_size = audio_format.GetFrameSize();
+
+ int error;
+ encoder->enc = opus_encoder_create(audio_format.sample_rate,
+ audio_format.channels,
+ OPUS_APPLICATION_AUDIO,
+ &error);
+ if (encoder->enc == nullptr) {
+ g_set_error_literal(error_r, opus_encoder_quark(), error,
+ opus_strerror(error));
+ return false;
+ }
+
+ opus_encoder_ctl(encoder->enc, OPUS_SET_BITRATE(encoder->bitrate));
+ opus_encoder_ctl(encoder->enc,
+ OPUS_SET_COMPLEXITY(encoder->complexity));
+ opus_encoder_ctl(encoder->enc, OPUS_SET_SIGNAL(encoder->signal));
+
+ opus_encoder_ctl(encoder->enc, OPUS_GET_LOOKAHEAD(&encoder->lookahead));
+
+ encoder->buffer_frames = audio_format.sample_rate / 50;
+ encoder->buffer_size = encoder->frame_size * encoder->buffer_frames;
+ encoder->buffer_position = 0;
+ encoder->buffer = (unsigned char *)g_malloc(encoder->buffer_size);
+
+ encoder->stream.Initialize(g_random_int());
+ encoder->packetno = 0;
+
+ return true;
+}
+
+static void
+opus_encoder_close(Encoder *_encoder)
+{
+ struct opus_encoder *encoder = (struct opus_encoder *)_encoder;
+
+ encoder->stream.Deinitialize();
+ g_free(encoder->buffer);
+ opus_encoder_destroy(encoder->enc);
+}
+
+static bool
+opus_encoder_do_encode(struct opus_encoder *encoder, bool eos,
+ GError **error_r)
+{
+ assert(encoder->buffer_position == encoder->buffer_size);
+
+ opus_int32 result =
+ encoder->audio_format.format == SampleFormat::S16
+ ? opus_encode(encoder->enc,
+ (const opus_int16 *)encoder->buffer,
+ encoder->buffer_frames,
+ encoder->buffer2,
+ sizeof(encoder->buffer2))
+ : opus_encode_float(encoder->enc,
+ (const float *)encoder->buffer,
+ encoder->buffer_frames,
+ encoder->buffer2,
+ sizeof(encoder->buffer2));
+ if (result < 0) {
+ g_set_error_literal(error_r, opus_encoder_quark(), 0,
+ "Opus encoder error");
+ return false;
+ }
+
+ encoder->granulepos += encoder->buffer_frames;
+
+ ogg_packet packet;
+ packet.packet = encoder->buffer2;
+ packet.bytes = result;
+ packet.b_o_s = false;
+ packet.e_o_s = eos;
+ packet.granulepos = encoder->granulepos;
+ packet.packetno = encoder->packetno++;
+ encoder->stream.PacketIn(packet);
+
+ encoder->buffer_position = 0;
+
+ return true;
+}
+
+static bool
+opus_encoder_end(Encoder *_encoder, GError **error_r)
+{
+ struct opus_encoder *encoder = (struct opus_encoder *)_encoder;
+
+ encoder->stream.Flush();
+
+ memset(encoder->buffer + encoder->buffer_position, 0,
+ encoder->buffer_size - encoder->buffer_position);
+ encoder->buffer_position = encoder->buffer_size;
+
+ return opus_encoder_do_encode(encoder, true, error_r);
+}
+
+static bool
+opus_encoder_flush(Encoder *_encoder, G_GNUC_UNUSED GError **error)
+{
+ struct opus_encoder *encoder = (struct opus_encoder *)_encoder;
+
+ encoder->stream.Flush();
+ return true;
+}
+
+static bool
+opus_encoder_write_silence(struct opus_encoder *encoder, unsigned fill_frames,
+ GError **error_r)
+{
+ size_t fill_bytes = fill_frames * encoder->frame_size;
+
+ while (fill_bytes > 0) {
+ size_t nbytes =
+ encoder->buffer_size - encoder->buffer_position;
+ if (nbytes > fill_bytes)
+ nbytes = fill_bytes;
+
+ memset(encoder->buffer + encoder->buffer_position,
+ 0, nbytes);
+ encoder->buffer_position += nbytes;
+ fill_bytes -= nbytes;
+
+ if (encoder->buffer_position == encoder->buffer_size &&
+ !opus_encoder_do_encode(encoder, false, error_r))
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+opus_encoder_write(Encoder *_encoder,
+ const void *_data, size_t length,
+ GError **error_r)
+{
+ struct opus_encoder *encoder = (struct opus_encoder *)_encoder;
+ const uint8_t *data = (const uint8_t *)_data;
+
+ if (encoder->lookahead > 0) {
+ /* generate some silence at the beginning of the
+ stream */
+
+ assert(encoder->buffer_position == 0);
+
+ if (!opus_encoder_write_silence(encoder, encoder->lookahead,
+ error_r))
+ return false;
+
+ encoder->lookahead = 0;
+ }
+
+ while (length > 0) {
+ size_t nbytes =
+ encoder->buffer_size - encoder->buffer_position;
+ if (nbytes > length)
+ nbytes = length;
+
+ memcpy(encoder->buffer + encoder->buffer_position,
+ data, nbytes);
+ data += nbytes;
+ length -= nbytes;
+ encoder->buffer_position += nbytes;
+
+ if (encoder->buffer_position == encoder->buffer_size &&
+ !opus_encoder_do_encode(encoder, false, error_r))
+ return false;
+ }
+
+ return true;
+}
+
+static void
+opus_encoder_generate_head(struct opus_encoder *encoder)
+{
+ unsigned char header[19];
+ memcpy(header, "OpusHead", 8);
+ header[8] = 1;
+ header[9] = encoder->audio_format.channels;
+ *(uint16_t *)(header + 10) = GUINT16_TO_LE(encoder->lookahead);
+ *(uint32_t *)(header + 12) =
+ GUINT32_TO_LE(encoder->audio_format.sample_rate);
+ header[16] = 0;
+ header[17] = 0;
+ header[18] = 0;
+
+ ogg_packet packet;
+ packet.packet = header;
+ packet.bytes = 19;
+ packet.b_o_s = true;
+ packet.e_o_s = false;
+ packet.granulepos = 0;
+ packet.packetno = encoder->packetno++;
+ encoder->stream.PacketIn(packet);
+ encoder->stream.Flush();
+}
+
+static void
+opus_encoder_generate_tags(struct opus_encoder *encoder)
+{
+ const char *version = opus_get_version_string();
+ size_t version_length = strlen(version);
+
+ size_t comments_size = 8 + 4 + version_length + 4;
+ unsigned char *comments = (unsigned char *)g_malloc(comments_size);
+ memcpy(comments, "OpusTags", 8);
+ *(uint32_t *)(comments + 8) = GUINT32_TO_LE(version_length);
+ memcpy(comments + 12, version, version_length);
+ *(uint32_t *)(comments + 12 + version_length) = GUINT32_TO_LE(0);
+
+ ogg_packet packet;
+ packet.packet = comments;
+ packet.bytes = comments_size;
+ packet.b_o_s = false;
+ packet.e_o_s = false;
+ packet.granulepos = 0;
+ packet.packetno = encoder->packetno++;
+ encoder->stream.PacketIn(packet);
+ encoder->stream.Flush();
+
+ g_free(comments);
+}
+
+static size_t
+opus_encoder_read(Encoder *_encoder, void *dest, size_t length)
+{
+ struct opus_encoder *encoder = (struct opus_encoder *)_encoder;
+
+ if (encoder->packetno == 0)
+ opus_encoder_generate_head(encoder);
+ else if (encoder->packetno == 1)
+ opus_encoder_generate_tags(encoder);
+
+ return encoder->stream.PageOut(dest, length);
+}
+
+static const char *
+opus_encoder_get_mime_type(G_GNUC_UNUSED Encoder *_encoder)
+{
+ return "audio/ogg";
+}
+
+const EncoderPlugin opus_encoder_plugin = {
+ "opus",
+ opus_encoder_init,
+ opus_encoder_finish,
+ opus_encoder_open,
+ opus_encoder_close,
+ opus_encoder_end,
+ opus_encoder_flush,
+ nullptr,
+ nullptr,
+ opus_encoder_write,
+ opus_encoder_read,
+ opus_encoder_get_mime_type,
+};
diff --git a/src/encoder/OpusEncoderPlugin.hxx b/src/encoder/OpusEncoderPlugin.hxx
new file mode 100644
index 000000000..d6da0e960
--- /dev/null
+++ b/src/encoder/OpusEncoderPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+#ifndef MPD_ENCODER_OPUS_H
+#define MPD_ENCODER_OPUS_H
+
+extern const struct EncoderPlugin opus_encoder_plugin;
+
+#endif
diff --git a/src/encoder/TwolameEncoderPlugin.cxx b/src/encoder/TwolameEncoderPlugin.cxx
new file mode 100644
index 000000000..cd85e43b6
--- /dev/null
+++ b/src/encoder/TwolameEncoderPlugin.cxx
@@ -0,0 +1,308 @@
+/*
+ * 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 "TwolameEncoderPlugin.hxx"
+#include "EncoderAPI.hxx"
+#include "AudioFormat.hxx"
+
+#include <twolame.h>
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+
+struct TwolameEncoder final {
+ Encoder encoder;
+
+ AudioFormat audio_format;
+ float quality;
+ int bitrate;
+
+ twolame_options *options;
+
+ unsigned char buffer[32768];
+ size_t buffer_length;
+
+ /**
+ * Call libtwolame's flush function when the buffer is empty?
+ */
+ bool flush;
+
+ TwolameEncoder():encoder(twolame_encoder_plugin) {}
+
+ bool Configure(const config_param &param, GError **error);
+};
+
+static inline GQuark
+twolame_encoder_quark(void)
+{
+ return g_quark_from_static_string("twolame_encoder");
+}
+
+bool
+TwolameEncoder::Configure(const config_param &param, GError **error)
+{
+ const char *value;
+ char *endptr;
+
+ value = param.GetBlockValue("quality");
+ if (value != nullptr) {
+ /* a quality was configured (VBR) */
+
+ quality = g_ascii_strtod(value, &endptr);
+
+ if (*endptr != '\0' || quality < -1.0 || quality > 10.0) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "quality \"%s\" is not a number in the "
+ "range -1 to 10, line %i",
+ value, param.line);
+ return false;
+ }
+
+ if (param.GetBlockValue("bitrate") != nullptr) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "quality and bitrate are "
+ "both defined (line %i)",
+ param.line);
+ return false;
+ }
+ } else {
+ /* a bit rate was configured */
+
+ value = param.GetBlockValue("bitrate");
+ if (value == nullptr) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "neither bitrate nor quality defined "
+ "at line %i",
+ param.line);
+ return false;
+ }
+
+ quality = -2.0;
+ bitrate = g_ascii_strtoll(value, &endptr, 10);
+
+ if (*endptr != '\0' || bitrate <= 0) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "bitrate at line %i should be a positive integer",
+ param.line);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static Encoder *
+twolame_encoder_init(const config_param &param, GError **error_r)
+{
+ g_debug("libtwolame version %s", get_twolame_version());
+
+ TwolameEncoder *encoder = new TwolameEncoder();
+
+ /* load configuration from "param" */
+ if (!encoder->Configure(param, error_r)) {
+ /* configuration has failed, roll back and return error */
+ delete encoder;
+ return nullptr;
+ }
+
+ return &encoder->encoder;
+}
+
+static void
+twolame_encoder_finish(Encoder *_encoder)
+{
+ TwolameEncoder *encoder = (TwolameEncoder *)_encoder;
+
+ /* the real libtwolame cleanup was already performed by
+ twolame_encoder_close(), so no real work here */
+ delete encoder;
+}
+
+static bool
+twolame_encoder_setup(TwolameEncoder *encoder, GError **error)
+{
+ if (encoder->quality >= -1.0) {
+ /* a quality was configured (VBR) */
+
+ if (0 != twolame_set_VBR(encoder->options, true)) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "error setting twolame VBR mode");
+ return false;
+ }
+ if (0 != twolame_set_VBR_q(encoder->options, encoder->quality)) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "error setting twolame VBR quality");
+ return false;
+ }
+ } else {
+ /* a bit rate was configured */
+
+ if (0 != twolame_set_brate(encoder->options, encoder->bitrate)) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "error setting twolame bitrate");
+ return false;
+ }
+ }
+
+ if (0 != twolame_set_num_channels(encoder->options,
+ encoder->audio_format.channels)) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "error setting twolame num channels");
+ return false;
+ }
+
+ if (0 != twolame_set_in_samplerate(encoder->options,
+ encoder->audio_format.sample_rate)) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "error setting twolame sample rate");
+ return false;
+ }
+
+ if (0 > twolame_init_params(encoder->options)) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "error initializing twolame params");
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+twolame_encoder_open(Encoder *_encoder, AudioFormat &audio_format,
+ GError **error)
+{
+ TwolameEncoder *encoder = (TwolameEncoder *)_encoder;
+
+ audio_format.format = SampleFormat::S16;
+ audio_format.channels = 2;
+
+ encoder->audio_format = audio_format;
+
+ encoder->options = twolame_init();
+ if (encoder->options == nullptr) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "twolame_init() failed");
+ return false;
+ }
+
+ if (!twolame_encoder_setup(encoder, error)) {
+ twolame_close(&encoder->options);
+ return false;
+ }
+
+ encoder->buffer_length = 0;
+ encoder->flush = false;
+
+ return true;
+}
+
+static void
+twolame_encoder_close(Encoder *_encoder)
+{
+ TwolameEncoder *encoder = (TwolameEncoder *)_encoder;
+
+ twolame_close(&encoder->options);
+}
+
+static bool
+twolame_encoder_flush(Encoder *_encoder, gcc_unused GError **error)
+{
+ TwolameEncoder *encoder = (TwolameEncoder *)_encoder;
+
+ encoder->flush = true;
+ return true;
+}
+
+static bool
+twolame_encoder_write(Encoder *_encoder,
+ const void *data, size_t length,
+ gcc_unused GError **error)
+{
+ TwolameEncoder *encoder = (TwolameEncoder *)_encoder;
+ const int16_t *src = (const int16_t*)data;
+
+ assert(encoder->buffer_length == 0);
+
+ const unsigned num_frames =
+ length / encoder->audio_format.GetFrameSize();
+
+ int bytes_out = twolame_encode_buffer_interleaved(encoder->options,
+ src, num_frames,
+ encoder->buffer,
+ sizeof(encoder->buffer));
+ if (bytes_out < 0) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "twolame encoder failed");
+ return false;
+ }
+
+ encoder->buffer_length = (size_t)bytes_out;
+ return true;
+}
+
+static size_t
+twolame_encoder_read(Encoder *_encoder, void *dest, size_t length)
+{
+ TwolameEncoder *encoder = (TwolameEncoder *)_encoder;
+
+ if (encoder->buffer_length == 0 && encoder->flush) {
+ int ret = twolame_encode_flush(encoder->options,
+ encoder->buffer,
+ sizeof(encoder->buffer));
+ if (ret > 0)
+ encoder->buffer_length = (size_t)ret;
+
+ encoder->flush = false;
+ }
+
+ if (length > encoder->buffer_length)
+ length = encoder->buffer_length;
+
+ memcpy(dest, encoder->buffer, length);
+
+ encoder->buffer_length -= length;
+ memmove(encoder->buffer, encoder->buffer + length,
+ encoder->buffer_length);
+
+ return length;
+}
+
+static const char *
+twolame_encoder_get_mime_type(gcc_unused Encoder *_encoder)
+{
+ return "audio/mpeg";
+}
+
+const EncoderPlugin twolame_encoder_plugin = {
+ "twolame",
+ twolame_encoder_init,
+ twolame_encoder_finish,
+ twolame_encoder_open,
+ twolame_encoder_close,
+ twolame_encoder_flush,
+ twolame_encoder_flush,
+ nullptr,
+ nullptr,
+ twolame_encoder_write,
+ twolame_encoder_read,
+ twolame_encoder_get_mime_type,
+};
diff --git a/src/encoder/TwolameEncoderPlugin.hxx b/src/encoder/TwolameEncoderPlugin.hxx
new file mode 100644
index 000000000..dd8a536f6
--- /dev/null
+++ b/src/encoder/TwolameEncoderPlugin.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_ENCODER_TWOLAME_HXX
+#define MPD_ENCODER_TWOLAME_HXX
+
+extern const struct EncoderPlugin twolame_encoder_plugin;
+
+#endif
diff --git a/src/encoder/VorbisEncoderPlugin.cxx b/src/encoder/VorbisEncoderPlugin.cxx
new file mode 100644
index 000000000..9d0ba9461
--- /dev/null
+++ b/src/encoder/VorbisEncoderPlugin.cxx
@@ -0,0 +1,373 @@
+/*
+ * 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 "VorbisEncoderPlugin.hxx"
+#include "OggStream.hxx"
+#include "EncoderAPI.hxx"
+#include "Tag.hxx"
+#include "AudioFormat.hxx"
+#include "mpd_error.h"
+
+#include <vorbis/vorbisenc.h>
+
+#include <assert.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "vorbis_encoder"
+
+struct vorbis_encoder {
+ /** the base class */
+ Encoder encoder;
+
+ /* configuration */
+
+ float quality;
+ int bitrate;
+
+ /* runtime information */
+
+ AudioFormat audio_format;
+
+ vorbis_dsp_state vd;
+ vorbis_block vb;
+ vorbis_info vi;
+
+ OggStream stream;
+
+ vorbis_encoder():encoder(vorbis_encoder_plugin) {}
+};
+
+static inline GQuark
+vorbis_encoder_quark(void)
+{
+ return g_quark_from_static_string("vorbis_encoder");
+}
+
+static bool
+vorbis_encoder_configure(struct vorbis_encoder *encoder,
+ const config_param &param, GError **error)
+{
+ const char *value = param.GetBlockValue("quality");
+ if (value != nullptr) {
+ /* a quality was configured (VBR) */
+
+ char *endptr;
+ encoder->quality = g_ascii_strtod(value, &endptr);
+
+ if (*endptr != '\0' || encoder->quality < -1.0 ||
+ encoder->quality > 10.0) {
+ g_set_error(error, vorbis_encoder_quark(), 0,
+ "quality \"%s\" is not a number in the "
+ "range -1 to 10, line %i",
+ value, param.line);
+ return false;
+ }
+
+ if (param.GetBlockValue("bitrate") != nullptr) {
+ g_set_error(error, vorbis_encoder_quark(), 0,
+ "quality and bitrate are "
+ "both defined (line %i)",
+ param.line);
+ return false;
+ }
+ } else {
+ /* a bit rate was configured */
+
+ value = param.GetBlockValue("bitrate");
+ if (value == nullptr) {
+ g_set_error(error, vorbis_encoder_quark(), 0,
+ "neither bitrate nor quality defined "
+ "at line %i",
+ param.line);
+ return false;
+ }
+
+ encoder->quality = -2.0;
+
+ char *endptr;
+ encoder->bitrate = g_ascii_strtoll(value, &endptr, 10);
+ if (*endptr != '\0' || encoder->bitrate <= 0) {
+ g_set_error(error, vorbis_encoder_quark(), 0,
+ "bitrate at line %i should be a positive integer",
+ param.line);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static Encoder *
+vorbis_encoder_init(const config_param &param, GError **error)
+{
+ vorbis_encoder *encoder = new vorbis_encoder();
+
+ /* load configuration from "param" */
+ if (!vorbis_encoder_configure(encoder, param, error)) {
+ /* configuration has failed, roll back and return error */
+ delete encoder;
+ return nullptr;
+ }
+
+ return &encoder->encoder;
+}
+
+static void
+vorbis_encoder_finish(Encoder *_encoder)
+{
+ struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder;
+
+ /* the real libvorbis/libogg cleanup was already performed by
+ vorbis_encoder_close(), so no real work here */
+ delete encoder;
+}
+
+static bool
+vorbis_encoder_reinit(struct vorbis_encoder *encoder, GError **error)
+{
+ vorbis_info_init(&encoder->vi);
+
+ if (encoder->quality >= -1.0) {
+ /* a quality was configured (VBR) */
+
+ if (0 != vorbis_encode_init_vbr(&encoder->vi,
+ encoder->audio_format.channels,
+ encoder->audio_format.sample_rate,
+ encoder->quality * 0.1)) {
+ g_set_error(error, vorbis_encoder_quark(), 0,
+ "error initializing vorbis vbr");
+ vorbis_info_clear(&encoder->vi);
+ return false;
+ }
+ } else {
+ /* a bit rate was configured */
+
+ if (0 != vorbis_encode_init(&encoder->vi,
+ encoder->audio_format.channels,
+ encoder->audio_format.sample_rate, -1.0,
+ encoder->bitrate * 1000, -1.0)) {
+ g_set_error(error, vorbis_encoder_quark(), 0,
+ "error initializing vorbis encoder");
+ vorbis_info_clear(&encoder->vi);
+ return false;
+ }
+ }
+
+ vorbis_analysis_init(&encoder->vd, &encoder->vi);
+ vorbis_block_init(&encoder->vd, &encoder->vb);
+ encoder->stream.Initialize(g_random_int());
+
+ return true;
+}
+
+static void
+vorbis_encoder_headerout(struct vorbis_encoder *encoder, vorbis_comment *vc)
+{
+ ogg_packet packet, comments, codebooks;
+
+ vorbis_analysis_headerout(&encoder->vd, vc,
+ &packet, &comments, &codebooks);
+
+ encoder->stream.PacketIn(packet);
+ encoder->stream.PacketIn(comments);
+ encoder->stream.PacketIn(codebooks);
+}
+
+static void
+vorbis_encoder_send_header(struct vorbis_encoder *encoder)
+{
+ vorbis_comment vc;
+
+ vorbis_comment_init(&vc);
+ vorbis_encoder_headerout(encoder, &vc);
+ vorbis_comment_clear(&vc);
+}
+
+static bool
+vorbis_encoder_open(Encoder *_encoder,
+ AudioFormat &audio_format,
+ GError **error)
+{
+ struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder;
+
+ audio_format.format = SampleFormat::FLOAT;
+
+ encoder->audio_format = audio_format;
+
+ if (!vorbis_encoder_reinit(encoder, error))
+ return false;
+
+ vorbis_encoder_send_header(encoder);
+
+ return true;
+}
+
+static void
+vorbis_encoder_clear(struct vorbis_encoder *encoder)
+{
+ encoder->stream.Deinitialize();
+ vorbis_block_clear(&encoder->vb);
+ vorbis_dsp_clear(&encoder->vd);
+ vorbis_info_clear(&encoder->vi);
+}
+
+static void
+vorbis_encoder_close(Encoder *_encoder)
+{
+ struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder;
+
+ vorbis_encoder_clear(encoder);
+}
+
+static void
+vorbis_encoder_blockout(struct vorbis_encoder *encoder)
+{
+ while (vorbis_analysis_blockout(&encoder->vd, &encoder->vb) == 1) {
+ vorbis_analysis(&encoder->vb, nullptr);
+ vorbis_bitrate_addblock(&encoder->vb);
+
+ ogg_packet packet;
+ while (vorbis_bitrate_flushpacket(&encoder->vd, &packet))
+ encoder->stream.PacketIn(packet);
+ }
+}
+
+static bool
+vorbis_encoder_flush(Encoder *_encoder, G_GNUC_UNUSED GError **error)
+{
+ struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder;
+
+ encoder->stream.Flush();
+ return true;
+}
+
+static bool
+vorbis_encoder_pre_tag(Encoder *_encoder, G_GNUC_UNUSED GError **error)
+{
+ struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder;
+
+ vorbis_analysis_wrote(&encoder->vd, 0);
+ vorbis_encoder_blockout(encoder);
+
+ /* reinitialize vorbis_dsp_state and vorbis_block to reset the
+ end-of-stream marker */
+ vorbis_block_clear(&encoder->vb);
+ vorbis_dsp_clear(&encoder->vd);
+ vorbis_analysis_init(&encoder->vd, &encoder->vi);
+ vorbis_block_init(&encoder->vd, &encoder->vb);
+
+ encoder->stream.Flush();
+ return true;
+}
+
+static void
+copy_tag_to_vorbis_comment(vorbis_comment *vc, const Tag *tag)
+{
+ for (unsigned i = 0; i < tag->num_items; i++) {
+ const TagItem &item = *tag->items[i];
+ char *name = g_ascii_strup(tag_item_names[item.type], -1);
+ vorbis_comment_add_tag(vc, name, item.value);
+ g_free(name);
+ }
+}
+
+static bool
+vorbis_encoder_tag(Encoder *_encoder, const Tag *tag,
+ G_GNUC_UNUSED GError **error)
+{
+ struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder;
+ vorbis_comment comment;
+
+ /* write the vorbis_comment object */
+
+ vorbis_comment_init(&comment);
+ copy_tag_to_vorbis_comment(&comment, tag);
+
+ /* reset ogg_stream_state and begin a new stream */
+
+ encoder->stream.Reinitialize(g_random_int());
+
+ /* send that vorbis_comment to the ogg_stream_state */
+
+ vorbis_encoder_headerout(encoder, &comment);
+ vorbis_comment_clear(&comment);
+
+ return true;
+}
+
+static void
+interleaved_to_vorbis_buffer(float **dest, const float *src,
+ unsigned num_frames, unsigned num_channels)
+{
+ for (unsigned i = 0; i < num_frames; i++)
+ for (unsigned j = 0; j < num_channels; j++)
+ dest[j][i] = *src++;
+}
+
+static bool
+vorbis_encoder_write(Encoder *_encoder,
+ const void *data, size_t length,
+ G_GNUC_UNUSED GError **error)
+{
+ struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder;
+
+ unsigned num_frames = length / encoder->audio_format.GetFrameSize();
+
+ /* this is for only 16-bit audio */
+
+ interleaved_to_vorbis_buffer(vorbis_analysis_buffer(&encoder->vd,
+ num_frames),
+ (const float *)data,
+ num_frames,
+ encoder->audio_format.channels);
+
+ vorbis_analysis_wrote(&encoder->vd, num_frames);
+ vorbis_encoder_blockout(encoder);
+ return true;
+}
+
+static size_t
+vorbis_encoder_read(Encoder *_encoder, void *dest, size_t length)
+{
+ struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder;
+
+ return encoder->stream.PageOut(dest, length);
+}
+
+static const char *
+vorbis_encoder_get_mime_type(G_GNUC_UNUSED Encoder *_encoder)
+{
+ return "audio/ogg";
+}
+
+const EncoderPlugin vorbis_encoder_plugin = {
+ "vorbis",
+ vorbis_encoder_init,
+ vorbis_encoder_finish,
+ vorbis_encoder_open,
+ vorbis_encoder_close,
+ vorbis_encoder_pre_tag,
+ vorbis_encoder_flush,
+ vorbis_encoder_pre_tag,
+ vorbis_encoder_tag,
+ vorbis_encoder_write,
+ vorbis_encoder_read,
+ vorbis_encoder_get_mime_type,
+};
diff --git a/src/encoder/VorbisEncoderPlugin.hxx b/src/encoder/VorbisEncoderPlugin.hxx
new file mode 100644
index 000000000..72cc44f5c
--- /dev/null
+++ b/src/encoder/VorbisEncoderPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+#ifndef MPD_ENCODER_VORBIS_H
+#define MPD_ENCODER_VORBIS_H
+
+extern const struct EncoderPlugin vorbis_encoder_plugin;
+
+#endif
diff --git a/src/encoder/WaveEncoderPlugin.cxx b/src/encoder/WaveEncoderPlugin.cxx
new file mode 100644
index 000000000..17560dfea
--- /dev/null
+++ b/src/encoder/WaveEncoderPlugin.cxx
@@ -0,0 +1,276 @@
+/*
+ * 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 "WaveEncoderPlugin.hxx"
+#include "EncoderAPI.hxx"
+#include "util/fifo_buffer.h"
+extern "C" {
+#include "util/growing_fifo.h"
+}
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+
+struct WaveEncoder {
+ Encoder encoder;
+ unsigned bits;
+
+ struct fifo_buffer *buffer;
+
+ WaveEncoder():encoder(wave_encoder_plugin) {}
+};
+
+struct wave_header {
+ uint32_t id_riff;
+ uint32_t riff_size;
+ uint32_t id_wave;
+ uint32_t id_fmt;
+ uint32_t fmt_size;
+ uint16_t format;
+ uint16_t channels;
+ uint32_t freq;
+ uint32_t byterate;
+ uint16_t blocksize;
+ uint16_t bits;
+ uint32_t id_data;
+ uint32_t data_size;
+};
+
+static void
+fill_wave_header(struct wave_header *header, int channels, int bits,
+ int freq, int block_size)
+{
+ int data_size = 0x0FFFFFFF;
+
+ /* constants */
+ header->id_riff = GUINT32_TO_LE(0x46464952);
+ header->id_wave = GUINT32_TO_LE(0x45564157);
+ header->id_fmt = GUINT32_TO_LE(0x20746d66);
+ header->id_data = GUINT32_TO_LE(0x61746164);
+
+ /* wave format */
+ header->format = GUINT16_TO_LE(1); // PCM_FORMAT
+ header->channels = GUINT16_TO_LE(channels);
+ header->bits = GUINT16_TO_LE(bits);
+ header->freq = GUINT32_TO_LE(freq);
+ header->blocksize = GUINT16_TO_LE(block_size);
+ header->byterate = GUINT32_TO_LE(freq * block_size);
+
+ /* chunk sizes (fake data length) */
+ header->fmt_size = GUINT32_TO_LE(16);
+ header->data_size = GUINT32_TO_LE(data_size);
+ header->riff_size = GUINT32_TO_LE(4 + (8 + 16) +
+ (8 + data_size));
+}
+
+static Encoder *
+wave_encoder_init(gcc_unused const config_param &param,
+ gcc_unused GError **error)
+{
+ WaveEncoder *encoder = new WaveEncoder();
+ return &encoder->encoder;
+}
+
+static void
+wave_encoder_finish(Encoder *_encoder)
+{
+ WaveEncoder *encoder = (WaveEncoder *)_encoder;
+
+ g_free(encoder);
+}
+
+static bool
+wave_encoder_open(Encoder *_encoder,
+ AudioFormat &audio_format,
+ gcc_unused GError **error)
+{
+ WaveEncoder *encoder = (WaveEncoder *)_encoder;
+
+ assert(audio_format.IsValid());
+
+ switch (audio_format.format) {
+ case SampleFormat::S8:
+ encoder->bits = 8;
+ break;
+
+ case SampleFormat::S16:
+ encoder->bits = 16;
+ break;
+
+ case SampleFormat::S24_P32:
+ encoder->bits = 24;
+ break;
+
+ case SampleFormat::S32:
+ encoder->bits = 32;
+ break;
+
+ default:
+ audio_format.format = SampleFormat::S16;
+ encoder->bits = 16;
+ break;
+ }
+
+ encoder->buffer = growing_fifo_new();
+ wave_header *header = (wave_header *)
+ growing_fifo_write(&encoder->buffer, sizeof(*header));
+
+ /* create PCM wave header in initial buffer */
+ fill_wave_header(header,
+ audio_format.channels,
+ encoder->bits,
+ audio_format.sample_rate,
+ (encoder->bits / 8) * audio_format.channels);
+ fifo_buffer_append(encoder->buffer, sizeof(*header));
+
+ return true;
+}
+
+static void
+wave_encoder_close(Encoder *_encoder)
+{
+ WaveEncoder *encoder = (WaveEncoder *)_encoder;
+
+ fifo_buffer_free(encoder->buffer);
+}
+
+static inline size_t
+pcm16_to_wave(uint16_t *dst16, const uint16_t *src16, size_t length)
+{
+ size_t cnt = length >> 1;
+ while (cnt > 0) {
+ *dst16++ = GUINT16_TO_LE(*src16++);
+ cnt--;
+ }
+ return length;
+}
+
+static inline size_t
+pcm32_to_wave(uint32_t *dst32, const uint32_t *src32, size_t length)
+{
+ size_t cnt = length >> 2;
+ while (cnt > 0){
+ *dst32++ = GUINT32_TO_LE(*src32++);
+ cnt--;
+ }
+ return length;
+}
+
+static inline size_t
+pcm24_to_wave(uint8_t *dst8, const uint32_t *src32, size_t length)
+{
+ uint32_t value;
+ uint8_t *dst_old = dst8;
+
+ length = length >> 2;
+ while (length > 0){
+ value = *src32++;
+ *dst8++ = (value) & 0xFF;
+ *dst8++ = (value >> 8) & 0xFF;
+ *dst8++ = (value >> 16) & 0xFF;
+ length--;
+ }
+ //correct buffer length
+ return (dst8 - dst_old);
+}
+
+static bool
+wave_encoder_write(Encoder *_encoder,
+ const void *src, size_t length,
+ gcc_unused GError **error)
+{
+ WaveEncoder *encoder = (WaveEncoder *)_encoder;
+
+ uint8_t *dst = (uint8_t *)growing_fifo_write(&encoder->buffer, length);
+
+#if (G_BYTE_ORDER == G_LITTLE_ENDIAN)
+ switch (encoder->bits) {
+ case 8:
+ case 16:
+ case 32:// optimized cases
+ memcpy(dst, src, length);
+ break;
+ case 24:
+ length = pcm24_to_wave(dst, (const uint32_t *)src, length);
+ break;
+ }
+#elif (G_BYTE_ORDER == G_BIG_ENDIAN)
+ switch (encoder->bits) {
+ case 8:
+ memcpy(dst, src, length);
+ break;
+ case 16:
+ length = pcm16_to_wave(dst, (const uint16_t *)src, length);
+ break;
+ case 24:
+ length = pcm24_to_wave(dst, (const uint32_t *)src, length);
+ break;
+ case 32:
+ length = pcm32_to_wave(dst, (const uint32_t *)src, length);
+ break;
+ }
+#else
+#error G_BYTE_ORDER set to G_PDP_ENDIAN is not supported by wave_encoder
+#endif
+
+ fifo_buffer_append(encoder->buffer, length);
+ return true;
+}
+
+static size_t
+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;
+}
+
+static const char *
+wave_encoder_get_mime_type(gcc_unused Encoder *_encoder)
+{
+ return "audio/wav";
+}
+
+const EncoderPlugin wave_encoder_plugin = {
+ "wave",
+ wave_encoder_init,
+ wave_encoder_finish,
+ wave_encoder_open,
+ wave_encoder_close,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ wave_encoder_write,
+ wave_encoder_read,
+ wave_encoder_get_mime_type,
+};
diff --git a/src/encoder/WaveEncoderPlugin.hxx b/src/encoder/WaveEncoderPlugin.hxx
new file mode 100644
index 000000000..190ee131e
--- /dev/null
+++ b/src/encoder/WaveEncoderPlugin.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_ENCODER_WAVE_HXX
+#define MPD_ENCODER_WAVE_HXX
+
+extern const struct EncoderPlugin wave_encoder_plugin;
+
+#endif
diff --git a/src/encoder/flac_encoder.c b/src/encoder/flac_encoder.c
deleted file mode 100644
index e32588e29..000000000
--- a/src/encoder/flac_encoder.c
+++ /dev/null
@@ -1,363 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "encoder_api.h"
-#include "encoder_plugin.h"
-#include "audio_format.h"
-#include "pcm_buffer.h"
-#include "fifo_buffer.h"
-#include "growing_fifo.h"
-
-#include <assert.h>
-#include <string.h>
-
-#include <FLAC/stream_encoder.h>
-
-struct flac_encoder {
- struct encoder encoder;
-
- struct audio_format audio_format;
- unsigned compression;
-
- FLAC__StreamEncoder *fse;
-
- struct pcm_buffer expand_buffer;
-
- /**
- * This buffer will hold encoded data from libFLAC until it is
- * picked up with flac_encoder_read().
- */
- struct fifo_buffer *output_buffer;
-};
-
-extern const struct encoder_plugin flac_encoder_plugin;
-
-
-static inline GQuark
-flac_encoder_quark(void)
-{
- return g_quark_from_static_string("flac_encoder");
-}
-
-static bool
-flac_encoder_configure(struct flac_encoder *encoder,
- const struct config_param *param, G_GNUC_UNUSED GError **error)
-{
- encoder->compression = config_get_block_unsigned(param,
- "compression", 5);
-
- return true;
-}
-
-static struct encoder *
-flac_encoder_init(const struct config_param *param, GError **error)
-{
- struct flac_encoder *encoder;
-
- encoder = g_new(struct flac_encoder, 1);
- encoder_struct_init(&encoder->encoder, &flac_encoder_plugin);
-
- /* load configuration from "param" */
- if (!flac_encoder_configure(encoder, param, error)) {
- /* configuration has failed, roll back and return error */
- g_free(encoder);
- return NULL;
- }
-
- return &encoder->encoder;
-}
-
-static void
-flac_encoder_finish(struct encoder *_encoder)
-{
- struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
-
- /* the real libFLAC cleanup was already performed by
- flac_encoder_close(), so no real work here */
- g_free(encoder);
-}
-
-static bool
-flac_encoder_setup(struct flac_encoder *encoder, unsigned bits_per_sample,
- GError **error)
-{
-#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7
-#else
- if ( !FLAC__stream_encoder_set_compression_level(encoder->fse,
- encoder->compression)) {
- g_set_error(error, flac_encoder_quark(), 0,
- "error setting flac compression to %d",
- encoder->compression);
- return false;
- }
-#endif
- if ( !FLAC__stream_encoder_set_channels(encoder->fse,
- encoder->audio_format.channels)) {
- g_set_error(error, flac_encoder_quark(), 0,
- "error setting flac channels num to %d",
- encoder->audio_format.channels);
- return false;
- }
- if ( !FLAC__stream_encoder_set_bits_per_sample(encoder->fse,
- bits_per_sample)) {
- g_set_error(error, flac_encoder_quark(), 0,
- "error setting flac bit format to %d",
- bits_per_sample);
- return false;
- }
- if ( !FLAC__stream_encoder_set_sample_rate(encoder->fse,
- encoder->audio_format.sample_rate)) {
- g_set_error(error, flac_encoder_quark(), 0,
- "error setting flac sample rate to %d",
- encoder->audio_format.sample_rate);
- return false;
- }
- return true;
-}
-
-static FLAC__StreamEncoderWriteStatus
-flac_write_callback(G_GNUC_UNUSED const FLAC__StreamEncoder *fse,
- const FLAC__byte data[],
-#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7
- unsigned bytes,
-#else
- size_t bytes,
-#endif
- G_GNUC_UNUSED unsigned samples,
- G_GNUC_UNUSED unsigned current_frame, void *client_data)
-{
- struct flac_encoder *encoder = (struct flac_encoder *) client_data;
-
- //transfer data to buffer
- growing_fifo_append(&encoder->output_buffer, data, bytes);
-
- return FLAC__STREAM_ENCODER_WRITE_STATUS_OK;
-}
-
-static void
-flac_encoder_close(struct encoder *_encoder)
-{
- struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
-
- FLAC__stream_encoder_delete(encoder->fse);
-
- pcm_buffer_deinit(&encoder->expand_buffer);
- fifo_buffer_free(encoder->output_buffer);
-}
-
-static bool
-flac_encoder_open(struct encoder *_encoder, struct audio_format *audio_format,
- GError **error)
-{
- struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
- unsigned bits_per_sample;
-
- encoder->audio_format = *audio_format;
-
- /* FIXME: flac should support 32bit as well */
- switch (audio_format->format) {
- case SAMPLE_FORMAT_S8:
- bits_per_sample = 8;
- break;
-
- case SAMPLE_FORMAT_S16:
- bits_per_sample = 16;
- break;
-
- case SAMPLE_FORMAT_S24_P32:
- bits_per_sample = 24;
- break;
-
- default:
- bits_per_sample = 24;
- audio_format->format = SAMPLE_FORMAT_S24_P32;
- }
-
- /* allocate the encoder */
- encoder->fse = FLAC__stream_encoder_new();
- if (encoder->fse == NULL) {
- g_set_error(error, flac_encoder_quark(), 0,
- "flac_new() failed");
- return false;
- }
-
- if (!flac_encoder_setup(encoder, bits_per_sample, error)) {
- FLAC__stream_encoder_delete(encoder->fse);
- return false;
- }
-
- pcm_buffer_init(&encoder->expand_buffer);
-
- encoder->output_buffer = growing_fifo_new();
-
- /* this immediately outputs data through callback */
-
-#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7
- {
- FLAC__StreamEncoderState init_status;
-
- FLAC__stream_encoder_set_write_callback(encoder->fse,
- flac_write_callback);
-
- init_status = FLAC__stream_encoder_init(encoder->fse);
-
- if (init_status != FLAC__STREAM_ENCODER_OK) {
- g_set_error(error, flac_encoder_quark(), 0,
- "failed to initialize encoder: %s\n",
- FLAC__StreamEncoderStateString[init_status]);
- flac_encoder_close(_encoder);
- return false;
- }
- }
-#else
- {
- FLAC__StreamEncoderInitStatus init_status;
-
- init_status = FLAC__stream_encoder_init_stream(encoder->fse,
- flac_write_callback,
- NULL, NULL, NULL, encoder);
-
- if(init_status != FLAC__STREAM_ENCODER_INIT_STATUS_OK) {
- g_set_error(error, flac_encoder_quark(), 0,
- "failed to initialize encoder: %s\n",
- FLAC__StreamEncoderInitStatusString[init_status]);
- flac_encoder_close(_encoder);
- return false;
- }
- }
-#endif
-
- return true;
-}
-
-
-static bool
-flac_encoder_flush(struct encoder *_encoder, G_GNUC_UNUSED GError **error)
-{
- struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
-
- (void) FLAC__stream_encoder_finish(encoder->fse);
- return true;
-}
-
-static inline void
-pcm8_to_flac(int32_t *out, const int8_t *in, unsigned num_samples)
-{
- while (num_samples > 0) {
- *out++ = *in++;
- --num_samples;
- }
-}
-
-static inline void
-pcm16_to_flac(int32_t *out, const int16_t *in, unsigned num_samples)
-{
- while (num_samples > 0) {
- *out++ = *in++;
- --num_samples;
- }
-}
-
-static bool
-flac_encoder_write(struct encoder *_encoder,
- const void *data, size_t length,
- G_GNUC_UNUSED GError **error)
-{
- struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
- unsigned num_frames, num_samples;
- void *exbuffer;
- const void *buffer = NULL;
-
- /* format conversion */
-
- num_frames = length / audio_format_frame_size(&encoder->audio_format);
- num_samples = num_frames * encoder->audio_format.channels;
-
- switch (encoder->audio_format.format) {
- case SAMPLE_FORMAT_S8:
- exbuffer = pcm_buffer_get(&encoder->expand_buffer, length*4);
- pcm8_to_flac(exbuffer, data, num_samples);
- buffer = exbuffer;
- break;
-
- case SAMPLE_FORMAT_S16:
- exbuffer = pcm_buffer_get(&encoder->expand_buffer, length*2);
- pcm16_to_flac(exbuffer, data, num_samples);
- buffer = exbuffer;
- break;
-
- case SAMPLE_FORMAT_S24_P32:
- case SAMPLE_FORMAT_S32:
- /* nothing need to be done; format is the same for
- both mpd and libFLAC */
- buffer = data;
- break;
- }
-
- /* feed samples to encoder */
-
- if (!FLAC__stream_encoder_process_interleaved(encoder->fse, buffer,
- num_frames)) {
- g_set_error(error, flac_encoder_quark(), 0,
- "flac encoder process failed");
- return false;
- }
-
- return true;
-}
-
-static size_t
-flac_encoder_read(struct encoder *_encoder, void *dest, size_t length)
-{
- struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
-
- size_t max_length;
- const char *src = fifo_buffer_read(encoder->output_buffer,
- &max_length);
- if (src == NULL)
- return 0;
-
- if (length > max_length)
- length = max_length;
-
- memcpy(dest, src, length);
- fifo_buffer_consume(encoder->output_buffer, length);
- return length;
-}
-
-static const char *
-flac_encoder_get_mime_type(G_GNUC_UNUSED struct encoder *_encoder)
-{
- return "audio/flac";
-}
-
-const struct encoder_plugin flac_encoder_plugin = {
- .name = "flac",
- .init = flac_encoder_init,
- .finish = flac_encoder_finish,
- .open = flac_encoder_open,
- .close = flac_encoder_close,
- .end = flac_encoder_flush,
- .flush = flac_encoder_flush,
- .write = flac_encoder_write,
- .read = flac_encoder_read,
- .get_mime_type = flac_encoder_get_mime_type,
-};
-
diff --git a/src/encoder/lame_encoder.c b/src/encoder/lame_encoder.c
deleted file mode 100644
index 3bb99ea28..000000000
--- a/src/encoder/lame_encoder.c
+++ /dev/null
@@ -1,300 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "encoder_api.h"
-#include "encoder_plugin.h"
-#include "audio_format.h"
-
-#include <lame/lame.h>
-#include <assert.h>
-#include <string.h>
-
-struct lame_encoder {
- struct encoder encoder;
-
- struct audio_format audio_format;
- float quality;
- int bitrate;
-
- lame_global_flags *gfp;
-
- unsigned char buffer[32768];
- size_t buffer_length;
-};
-
-extern const struct encoder_plugin lame_encoder_plugin;
-
-static inline GQuark
-lame_encoder_quark(void)
-{
- return g_quark_from_static_string("lame_encoder");
-}
-
-static bool
-lame_encoder_configure(struct lame_encoder *encoder,
- const struct config_param *param, GError **error)
-{
- const char *value;
- char *endptr;
-
- value = config_get_block_string(param, "quality", NULL);
- if (value != NULL) {
- /* a quality was configured (VBR) */
-
- encoder->quality = g_ascii_strtod(value, &endptr);
-
- if (*endptr != '\0' || encoder->quality < -1.0 ||
- encoder->quality > 10.0) {
- g_set_error(error, lame_encoder_quark(), 0,
- "quality \"%s\" is not a number in the "
- "range -1 to 10, line %i",
- value, param->line);
- return false;
- }
-
- if (config_get_block_string(param, "bitrate", NULL) != NULL) {
- g_set_error(error, lame_encoder_quark(), 0,
- "quality and bitrate are "
- "both defined (line %i)",
- param->line);
- return false;
- }
- } else {
- /* a bit rate was configured */
-
- value = config_get_block_string(param, "bitrate", NULL);
- if (value == NULL) {
- g_set_error(error, lame_encoder_quark(), 0,
- "neither bitrate nor quality defined "
- "at line %i",
- param->line);
- return false;
- }
-
- encoder->quality = -2.0;
- encoder->bitrate = g_ascii_strtoll(value, &endptr, 10);
-
- if (*endptr != '\0' || encoder->bitrate <= 0) {
- g_set_error(error, lame_encoder_quark(), 0,
- "bitrate at line %i should be a positive integer",
- param->line);
- return false;
- }
- }
-
- return true;
-}
-
-static struct encoder *
-lame_encoder_init(const struct config_param *param, GError **error)
-{
- struct lame_encoder *encoder;
-
- encoder = g_new(struct lame_encoder, 1);
- encoder_struct_init(&encoder->encoder, &lame_encoder_plugin);
-
- /* load configuration from "param" */
- if (!lame_encoder_configure(encoder, param, error)) {
- /* configuration has failed, roll back and return error */
- g_free(encoder);
- return NULL;
- }
-
- return &encoder->encoder;
-}
-
-static void
-lame_encoder_finish(struct encoder *_encoder)
-{
- struct lame_encoder *encoder = (struct lame_encoder *)_encoder;
-
- /* the real liblame cleanup was already performed by
- lame_encoder_close(), so no real work here */
- g_free(encoder);
-}
-
-static bool
-lame_encoder_setup(struct lame_encoder *encoder, GError **error)
-{
- if (encoder->quality >= -1.0) {
- /* a quality was configured (VBR) */
-
- if (0 != lame_set_VBR(encoder->gfp, vbr_rh)) {
- g_set_error(error, lame_encoder_quark(), 0,
- "error setting lame VBR mode");
- return false;
- }
- if (0 != lame_set_VBR_q(encoder->gfp, encoder->quality)) {
- g_set_error(error, lame_encoder_quark(), 0,
- "error setting lame VBR quality");
- return false;
- }
- } else {
- /* a bit rate was configured */
-
- if (0 != lame_set_brate(encoder->gfp, encoder->bitrate)) {
- g_set_error(error, lame_encoder_quark(), 0,
- "error setting lame bitrate");
- return false;
- }
- }
-
- if (0 != lame_set_num_channels(encoder->gfp,
- encoder->audio_format.channels)) {
- g_set_error(error, lame_encoder_quark(), 0,
- "error setting lame num channels");
- return false;
- }
-
- if (0 != lame_set_in_samplerate(encoder->gfp,
- encoder->audio_format.sample_rate)) {
- g_set_error(error, lame_encoder_quark(), 0,
- "error setting lame sample rate");
- return false;
- }
-
- if (0 != lame_set_out_samplerate(encoder->gfp,
- encoder->audio_format.sample_rate)) {
- g_set_error(error, lame_encoder_quark(), 0,
- "error setting lame out sample rate");
- return false;
- }
-
- if (0 > lame_init_params(encoder->gfp)) {
- g_set_error(error, lame_encoder_quark(), 0,
- "error initializing lame params");
- return false;
- }
-
- return true;
-}
-
-static bool
-lame_encoder_open(struct encoder *_encoder, struct audio_format *audio_format,
- GError **error)
-{
- struct lame_encoder *encoder = (struct lame_encoder *)_encoder;
-
- audio_format->format = SAMPLE_FORMAT_S16;
- audio_format->channels = 2;
-
- encoder->audio_format = *audio_format;
-
- encoder->gfp = lame_init();
- if (encoder->gfp == NULL) {
- g_set_error(error, lame_encoder_quark(), 0,
- "lame_init() failed");
- return false;
- }
-
- if (!lame_encoder_setup(encoder, error)) {
- lame_close(encoder->gfp);
- return false;
- }
-
- encoder->buffer_length = 0;
-
- return true;
-}
-
-static void
-lame_encoder_close(struct encoder *_encoder)
-{
- struct lame_encoder *encoder = (struct lame_encoder *)_encoder;
-
- lame_close(encoder->gfp);
-}
-
-static bool
-lame_encoder_write(struct encoder *_encoder,
- const void *data, size_t length,
- G_GNUC_UNUSED GError **error)
-{
- struct lame_encoder *encoder = (struct lame_encoder *)_encoder;
- unsigned num_frames;
- float *left, *right;
- const int16_t *src = (const int16_t*)data;
- unsigned int i;
- int bytes_out;
-
- assert(encoder->buffer_length == 0);
-
- num_frames =
- length / audio_format_frame_size(&encoder->audio_format);
- left = g_malloc(sizeof(left[0]) * num_frames);
- right = g_malloc(sizeof(right[0]) * num_frames);
-
- /* this is for only 16-bit audio */
-
- for (i = 0; i < num_frames; i++) {
- left[i] = *src++;
- right[i] = *src++;
- }
-
- bytes_out = lame_encode_buffer_float(encoder->gfp, left, right,
- num_frames, encoder->buffer,
- sizeof(encoder->buffer));
-
- g_free(left);
- g_free(right);
-
- if (bytes_out < 0) {
- g_set_error(error, lame_encoder_quark(), 0,
- "lame encoder failed");
- return false;
- }
-
- encoder->buffer_length = (size_t)bytes_out;
- return true;
-}
-
-static size_t
-lame_encoder_read(struct encoder *_encoder, void *dest, size_t length)
-{
- struct lame_encoder *encoder = (struct lame_encoder *)_encoder;
-
- if (length > encoder->buffer_length)
- length = encoder->buffer_length;
-
- memcpy(dest, encoder->buffer, length);
-
- encoder->buffer_length -= length;
- memmove(encoder->buffer, encoder->buffer + length,
- encoder->buffer_length);
-
- return length;
-}
-
-static const char *
-lame_encoder_get_mime_type(G_GNUC_UNUSED struct encoder *_encoder)
-{
- return "audio/mpeg";
-}
-
-const struct encoder_plugin lame_encoder_plugin = {
- .name = "lame",
- .init = lame_encoder_init,
- .finish = lame_encoder_finish,
- .open = lame_encoder_open,
- .close = lame_encoder_close,
- .write = lame_encoder_write,
- .read = lame_encoder_read,
- .get_mime_type = lame_encoder_get_mime_type,
-};
diff --git a/src/encoder/null_encoder.c b/src/encoder/null_encoder.c
deleted file mode 100644
index 48cdf139b..000000000
--- a/src/encoder/null_encoder.c
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "encoder_api.h"
-#include "encoder_plugin.h"
-#include "fifo_buffer.h"
-#include "growing_fifo.h"
-
-#include <assert.h>
-#include <string.h>
-
-struct null_encoder {
- struct encoder encoder;
-
- struct fifo_buffer *buffer;
-};
-
-extern const struct encoder_plugin null_encoder_plugin;
-
-static inline GQuark
-null_encoder_quark(void)
-{
- return g_quark_from_static_string("null_encoder");
-}
-
-static struct encoder *
-null_encoder_init(G_GNUC_UNUSED const struct config_param *param,
- G_GNUC_UNUSED GError **error)
-{
- struct null_encoder *encoder;
-
- encoder = g_new(struct null_encoder, 1);
- encoder_struct_init(&encoder->encoder, &null_encoder_plugin);
-
- return &encoder->encoder;
-}
-
-static void
-null_encoder_finish(struct encoder *_encoder)
-{
- struct null_encoder *encoder = (struct null_encoder *)_encoder;
-
- g_free(encoder);
-}
-
-static void
-null_encoder_close(struct encoder *_encoder)
-{
- struct null_encoder *encoder = (struct null_encoder *)_encoder;
-
- fifo_buffer_free(encoder->buffer);
-}
-
-
-static bool
-null_encoder_open(struct encoder *_encoder,
- G_GNUC_UNUSED struct audio_format *audio_format,
- G_GNUC_UNUSED GError **error)
-{
- struct null_encoder *encoder = (struct null_encoder *)_encoder;
-
- encoder->buffer = growing_fifo_new();
- return true;
-}
-
-static bool
-null_encoder_write(struct encoder *_encoder,
- const void *data, size_t length,
- G_GNUC_UNUSED GError **error)
-{
- struct null_encoder *encoder = (struct null_encoder *)_encoder;
-
- growing_fifo_append(&encoder->buffer, data, length);
- return length;
-}
-
-static size_t
-null_encoder_read(struct encoder *_encoder, void *dest, size_t length)
-{
- struct null_encoder *encoder = (struct null_encoder *)_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;
-}
-
-const struct encoder_plugin null_encoder_plugin = {
- .name = "null",
- .init = null_encoder_init,
- .finish = null_encoder_finish,
- .open = null_encoder_open,
- .close = null_encoder_close,
- .write = null_encoder_write,
- .read = null_encoder_read,
-};
diff --git a/src/encoder/twolame_encoder.c b/src/encoder/twolame_encoder.c
deleted file mode 100644
index 934b2ab24..000000000
--- a/src/encoder/twolame_encoder.c
+++ /dev/null
@@ -1,308 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "encoder_api.h"
-#include "encoder_plugin.h"
-#include "audio_format.h"
-
-#include <twolame.h>
-#include <assert.h>
-#include <string.h>
-
-struct twolame_encoder {
- struct encoder encoder;
-
- struct audio_format audio_format;
- float quality;
- int bitrate;
-
- twolame_options *options;
-
- unsigned char buffer[32768];
- size_t buffer_length;
-
- /**
- * Call libtwolame's flush function when the buffer is empty?
- */
- bool flush;
-};
-
-extern const struct encoder_plugin twolame_encoder_plugin;
-
-static inline GQuark
-twolame_encoder_quark(void)
-{
- return g_quark_from_static_string("twolame_encoder");
-}
-
-static bool
-twolame_encoder_configure(struct twolame_encoder *encoder,
- const struct config_param *param, GError **error)
-{
- const char *value;
- char *endptr;
-
- value = config_get_block_string(param, "quality", NULL);
- if (value != NULL) {
- /* a quality was configured (VBR) */
-
- encoder->quality = g_ascii_strtod(value, &endptr);
-
- if (*endptr != '\0' || encoder->quality < -1.0 ||
- encoder->quality > 10.0) {
- g_set_error(error, twolame_encoder_quark(), 0,
- "quality \"%s\" is not a number in the "
- "range -1 to 10, line %i",
- value, param->line);
- return false;
- }
-
- if (config_get_block_string(param, "bitrate", NULL) != NULL) {
- g_set_error(error, twolame_encoder_quark(), 0,
- "quality and bitrate are "
- "both defined (line %i)",
- param->line);
- return false;
- }
- } else {
- /* a bit rate was configured */
-
- value = config_get_block_string(param, "bitrate", NULL);
- if (value == NULL) {
- g_set_error(error, twolame_encoder_quark(), 0,
- "neither bitrate nor quality defined "
- "at line %i",
- param->line);
- return false;
- }
-
- encoder->quality = -2.0;
- encoder->bitrate = g_ascii_strtoll(value, &endptr, 10);
-
- if (*endptr != '\0' || encoder->bitrate <= 0) {
- g_set_error(error, twolame_encoder_quark(), 0,
- "bitrate at line %i should be a positive integer",
- param->line);
- return false;
- }
- }
-
- return true;
-}
-
-static struct encoder *
-twolame_encoder_init(const struct config_param *param, GError **error)
-{
- struct twolame_encoder *encoder;
-
- g_debug("libtwolame version %s", get_twolame_version());
-
- encoder = g_new(struct twolame_encoder, 1);
- encoder_struct_init(&encoder->encoder, &twolame_encoder_plugin);
-
- /* load configuration from "param" */
- if (!twolame_encoder_configure(encoder, param, error)) {
- /* configuration has failed, roll back and return error */
- g_free(encoder);
- return NULL;
- }
-
- return &encoder->encoder;
-}
-
-static void
-twolame_encoder_finish(struct encoder *_encoder)
-{
- struct twolame_encoder *encoder = (struct twolame_encoder *)_encoder;
-
- /* the real libtwolame cleanup was already performed by
- twolame_encoder_close(), so no real work here */
- g_free(encoder);
-}
-
-static bool
-twolame_encoder_setup(struct twolame_encoder *encoder, GError **error)
-{
- if (encoder->quality >= -1.0) {
- /* a quality was configured (VBR) */
-
- if (0 != twolame_set_VBR(encoder->options, true)) {
- g_set_error(error, twolame_encoder_quark(), 0,
- "error setting twolame VBR mode");
- return false;
- }
- if (0 != twolame_set_VBR_q(encoder->options, encoder->quality)) {
- g_set_error(error, twolame_encoder_quark(), 0,
- "error setting twolame VBR quality");
- return false;
- }
- } else {
- /* a bit rate was configured */
-
- if (0 != twolame_set_brate(encoder->options, encoder->bitrate)) {
- g_set_error(error, twolame_encoder_quark(), 0,
- "error setting twolame bitrate");
- return false;
- }
- }
-
- if (0 != twolame_set_num_channels(encoder->options,
- encoder->audio_format.channels)) {
- g_set_error(error, twolame_encoder_quark(), 0,
- "error setting twolame num channels");
- return false;
- }
-
- if (0 != twolame_set_in_samplerate(encoder->options,
- encoder->audio_format.sample_rate)) {
- g_set_error(error, twolame_encoder_quark(), 0,
- "error setting twolame sample rate");
- return false;
- }
-
- if (0 > twolame_init_params(encoder->options)) {
- g_set_error(error, twolame_encoder_quark(), 0,
- "error initializing twolame params");
- return false;
- }
-
- return true;
-}
-
-static bool
-twolame_encoder_open(struct encoder *_encoder, struct audio_format *audio_format,
- GError **error)
-{
- struct twolame_encoder *encoder = (struct twolame_encoder *)_encoder;
-
- audio_format->format = SAMPLE_FORMAT_S16;
- audio_format->channels = 2;
-
- encoder->audio_format = *audio_format;
-
- encoder->options = twolame_init();
- if (encoder->options == NULL) {
- g_set_error(error, twolame_encoder_quark(), 0,
- "twolame_init() failed");
- return false;
- }
-
- if (!twolame_encoder_setup(encoder, error)) {
- twolame_close(&encoder->options);
- return false;
- }
-
- encoder->buffer_length = 0;
- encoder->flush = false;
-
- return true;
-}
-
-static void
-twolame_encoder_close(struct encoder *_encoder)
-{
- struct twolame_encoder *encoder = (struct twolame_encoder *)_encoder;
-
- twolame_close(&encoder->options);
-}
-
-static bool
-twolame_encoder_flush(struct encoder *_encoder, G_GNUC_UNUSED GError **error)
-{
- struct twolame_encoder *encoder = (struct twolame_encoder *)_encoder;
-
- encoder->flush = true;
- return true;
-}
-
-static bool
-twolame_encoder_write(struct encoder *_encoder,
- const void *data, size_t length,
- G_GNUC_UNUSED GError **error)
-{
- struct twolame_encoder *encoder = (struct twolame_encoder *)_encoder;
- unsigned num_frames;
- const int16_t *src = (const int16_t*)data;
- int bytes_out;
-
- assert(encoder->buffer_length == 0);
-
- num_frames =
- length / audio_format_frame_size(&encoder->audio_format);
-
- bytes_out = twolame_encode_buffer_interleaved(encoder->options,
- src, num_frames,
- encoder->buffer,
- sizeof(encoder->buffer));
- if (bytes_out < 0) {
- g_set_error(error, twolame_encoder_quark(), 0,
- "twolame encoder failed");
- return false;
- }
-
- encoder->buffer_length = (size_t)bytes_out;
- return true;
-}
-
-static size_t
-twolame_encoder_read(struct encoder *_encoder, void *dest, size_t length)
-{
- struct twolame_encoder *encoder = (struct twolame_encoder *)_encoder;
-
- if (encoder->buffer_length == 0 && encoder->flush) {
- int ret = twolame_encode_flush(encoder->options,
- encoder->buffer,
- sizeof(encoder->buffer));
- if (ret > 0)
- encoder->buffer_length = (size_t)ret;
-
- encoder->flush = false;
- }
-
- if (length > encoder->buffer_length)
- length = encoder->buffer_length;
-
- memcpy(dest, encoder->buffer, length);
-
- encoder->buffer_length -= length;
- memmove(encoder->buffer, encoder->buffer + length,
- encoder->buffer_length);
-
- return length;
-}
-
-static const char *
-twolame_encoder_get_mime_type(G_GNUC_UNUSED struct encoder *_encoder)
-{
- return "audio/mpeg";
-}
-
-const struct encoder_plugin twolame_encoder_plugin = {
- .name = "twolame",
- .init = twolame_encoder_init,
- .finish = twolame_encoder_finish,
- .open = twolame_encoder_open,
- .close = twolame_encoder_close,
- .end = twolame_encoder_flush,
- .flush = twolame_encoder_flush,
- .write = twolame_encoder_write,
- .read = twolame_encoder_read,
- .get_mime_type = twolame_encoder_get_mime_type,
-};
diff --git a/src/encoder/vorbis_encoder.c b/src/encoder/vorbis_encoder.c
deleted file mode 100644
index 468cf38ee..000000000
--- a/src/encoder/vorbis_encoder.c
+++ /dev/null
@@ -1,407 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "encoder_api.h"
-#include "encoder_plugin.h"
-#include "tag.h"
-#include "audio_format.h"
-#include "mpd_error.h"
-
-#include <vorbis/vorbisenc.h>
-
-#include <assert.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "vorbis_encoder"
-
-struct vorbis_encoder {
- /** the base class */
- struct encoder encoder;
-
- /* configuration */
-
- float quality;
- int bitrate;
-
- /* runtime information */
-
- struct audio_format audio_format;
-
- ogg_stream_state os;
-
- vorbis_dsp_state vd;
- vorbis_block vb;
- vorbis_info vi;
-
- bool flush;
-};
-
-extern const struct encoder_plugin vorbis_encoder_plugin;
-
-static inline GQuark
-vorbis_encoder_quark(void)
-{
- return g_quark_from_static_string("vorbis_encoder");
-}
-
-static bool
-vorbis_encoder_configure(struct vorbis_encoder *encoder,
- const struct config_param *param, GError **error)
-{
- const char *value = config_get_block_string(param, "quality", NULL);
- if (value != NULL) {
- /* a quality was configured (VBR) */
-
- char *endptr;
- encoder->quality = g_ascii_strtod(value, &endptr);
-
- if (*endptr != '\0' || encoder->quality < -1.0 ||
- encoder->quality > 10.0) {
- g_set_error(error, vorbis_encoder_quark(), 0,
- "quality \"%s\" is not a number in the "
- "range -1 to 10, line %i",
- value, param->line);
- return false;
- }
-
- if (config_get_block_string(param, "bitrate", NULL) != NULL) {
- g_set_error(error, vorbis_encoder_quark(), 0,
- "quality and bitrate are "
- "both defined (line %i)",
- param->line);
- return false;
- }
- } else {
- /* a bit rate was configured */
-
- value = config_get_block_string(param, "bitrate", NULL);
- if (value == NULL) {
- g_set_error(error, vorbis_encoder_quark(), 0,
- "neither bitrate nor quality defined "
- "at line %i",
- param->line);
- return false;
- }
-
- encoder->quality = -2.0;
-
- char *endptr;
- encoder->bitrate = g_ascii_strtoll(value, &endptr, 10);
- if (*endptr != '\0' || encoder->bitrate <= 0) {
- g_set_error(error, vorbis_encoder_quark(), 0,
- "bitrate at line %i should be a positive integer",
- param->line);
- return false;
- }
- }
-
- return true;
-}
-
-static struct encoder *
-vorbis_encoder_init(const struct config_param *param, GError **error)
-{
- struct vorbis_encoder *encoder = g_new(struct vorbis_encoder, 1);
- encoder_struct_init(&encoder->encoder, &vorbis_encoder_plugin);
-
- /* load configuration from "param" */
- if (!vorbis_encoder_configure(encoder, param, error)) {
- /* configuration has failed, roll back and return error */
- g_free(encoder);
- return NULL;
- }
-
- return &encoder->encoder;
-}
-
-static void
-vorbis_encoder_finish(struct encoder *_encoder)
-{
- struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder;
-
- /* the real libvorbis/libogg cleanup was already performed by
- vorbis_encoder_close(), so no real work here */
- g_free(encoder);
-}
-
-static bool
-vorbis_encoder_reinit(struct vorbis_encoder *encoder, GError **error)
-{
- vorbis_info_init(&encoder->vi);
-
- if (encoder->quality >= -1.0) {
- /* a quality was configured (VBR) */
-
- if (0 != vorbis_encode_init_vbr(&encoder->vi,
- encoder->audio_format.channels,
- encoder->audio_format.sample_rate,
- encoder->quality * 0.1)) {
- g_set_error(error, vorbis_encoder_quark(), 0,
- "error initializing vorbis vbr");
- vorbis_info_clear(&encoder->vi);
- return false;
- }
- } else {
- /* a bit rate was configured */
-
- if (0 != vorbis_encode_init(&encoder->vi,
- encoder->audio_format.channels,
- encoder->audio_format.sample_rate, -1.0,
- encoder->bitrate * 1000, -1.0)) {
- g_set_error(error, vorbis_encoder_quark(), 0,
- "error initializing vorbis encoder");
- vorbis_info_clear(&encoder->vi);
- return false;
- }
- }
-
- vorbis_analysis_init(&encoder->vd, &encoder->vi);
- vorbis_block_init(&encoder->vd, &encoder->vb);
- ogg_stream_init(&encoder->os, g_random_int());
-
- return true;
-}
-
-static void
-vorbis_encoder_headerout(struct vorbis_encoder *encoder, vorbis_comment *vc)
-{
- ogg_packet packet, comments, codebooks;
-
- vorbis_analysis_headerout(&encoder->vd, vc,
- &packet, &comments, &codebooks);
-
- ogg_stream_packetin(&encoder->os, &packet);
- ogg_stream_packetin(&encoder->os, &comments);
- ogg_stream_packetin(&encoder->os, &codebooks);
-}
-
-static void
-vorbis_encoder_send_header(struct vorbis_encoder *encoder)
-{
- vorbis_comment vc;
-
- vorbis_comment_init(&vc);
- vorbis_encoder_headerout(encoder, &vc);
- vorbis_comment_clear(&vc);
-}
-
-static bool
-vorbis_encoder_open(struct encoder *_encoder,
- struct audio_format *audio_format,
- GError **error)
-{
- struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder;
-
- audio_format->format = SAMPLE_FORMAT_S16;
-
- encoder->audio_format = *audio_format;
-
- if (!vorbis_encoder_reinit(encoder, error))
- return false;
-
- vorbis_encoder_send_header(encoder);
-
- /* set "flush" to true, so the caller gets the full headers on
- the first read() */
- encoder->flush = true;
-
- return true;
-}
-
-static void
-vorbis_encoder_clear(struct vorbis_encoder *encoder)
-{
- ogg_stream_clear(&encoder->os);
- vorbis_block_clear(&encoder->vb);
- vorbis_dsp_clear(&encoder->vd);
- vorbis_info_clear(&encoder->vi);
-}
-
-static void
-vorbis_encoder_close(struct encoder *_encoder)
-{
- struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder;
-
- vorbis_encoder_clear(encoder);
-}
-
-static void
-vorbis_encoder_blockout(struct vorbis_encoder *encoder)
-{
- while (vorbis_analysis_blockout(&encoder->vd, &encoder->vb) == 1) {
- vorbis_analysis(&encoder->vb, NULL);
- vorbis_bitrate_addblock(&encoder->vb);
-
- ogg_packet packet;
- while (vorbis_bitrate_flushpacket(&encoder->vd, &packet))
- ogg_stream_packetin(&encoder->os, &packet);
- }
-}
-
-static bool
-vorbis_encoder_flush(struct encoder *_encoder, G_GNUC_UNUSED GError **error)
-{
- struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder;
-
- encoder->flush = true;
- return true;
-}
-
-static bool
-vorbis_encoder_pre_tag(struct encoder *_encoder, G_GNUC_UNUSED GError **error)
-{
- struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder;
-
- vorbis_analysis_wrote(&encoder->vd, 0);
- vorbis_encoder_blockout(encoder);
-
- /* reinitialize vorbis_dsp_state and vorbis_block to reset the
- end-of-stream marker */
- vorbis_block_clear(&encoder->vb);
- vorbis_dsp_clear(&encoder->vd);
- vorbis_analysis_init(&encoder->vd, &encoder->vi);
- vorbis_block_init(&encoder->vd, &encoder->vb);
-
- encoder->flush = true;
- return true;
-}
-
-static void
-copy_tag_to_vorbis_comment(vorbis_comment *vc, const struct tag *tag)
-{
- for (unsigned i = 0; i < tag->num_items; i++) {
- struct tag_item *item = tag->items[i];
- char *name = g_ascii_strup(tag_item_names[item->type], -1);
- vorbis_comment_add_tag(vc, name, item->value);
- g_free(name);
- }
-}
-
-static bool
-vorbis_encoder_tag(struct encoder *_encoder, const struct tag *tag,
- G_GNUC_UNUSED GError **error)
-{
- struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder;
- vorbis_comment comment;
-
- /* write the vorbis_comment object */
-
- vorbis_comment_init(&comment);
- copy_tag_to_vorbis_comment(&comment, tag);
-
- /* reset ogg_stream_state and begin a new stream */
-
- ogg_stream_reset_serialno(&encoder->os, g_random_int());
-
- /* send that vorbis_comment to the ogg_stream_state */
-
- vorbis_encoder_headerout(encoder, &comment);
- vorbis_comment_clear(&comment);
-
- /* the next vorbis_encoder_read() call should flush the
- ogg_stream_state */
-
- encoder->flush = true;
-
- return true;
-}
-
-static void
-pcm16_to_vorbis_buffer(float **dest, const int16_t *src,
- unsigned num_frames, unsigned num_channels)
-{
- for (unsigned i = 0; i < num_frames; i++)
- for (unsigned j = 0; j < num_channels; j++)
- dest[j][i] = *src++ / 32768.0;
-}
-
-static bool
-vorbis_encoder_write(struct encoder *_encoder,
- const void *data, size_t length,
- G_GNUC_UNUSED GError **error)
-{
- struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder;
-
- unsigned num_frames = length
- / audio_format_frame_size(&encoder->audio_format);
-
- /* this is for only 16-bit audio */
-
- pcm16_to_vorbis_buffer(vorbis_analysis_buffer(&encoder->vd,
- num_frames),
- (const int16_t *)data,
- num_frames, encoder->audio_format.channels);
-
- vorbis_analysis_wrote(&encoder->vd, num_frames);
- vorbis_encoder_blockout(encoder);
- return true;
-}
-
-static size_t
-vorbis_encoder_read(struct encoder *_encoder, void *_dest, size_t length)
-{
- struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder;
- unsigned char *dest = _dest;
-
- ogg_page page;
- int ret = ogg_stream_pageout(&encoder->os, &page);
- if (ret == 0 && encoder->flush) {
- encoder->flush = false;
- ret = ogg_stream_flush(&encoder->os, &page);
-
- }
-
- if (ret == 0)
- return 0;
-
- assert(page.header_len > 0 || page.body_len > 0);
-
- size_t nbytes = (size_t)page.header_len + (size_t)page.body_len;
-
- if (nbytes > length)
- /* XXX better error handling */
- MPD_ERROR("buffer too small");
-
- memcpy(dest, page.header, page.header_len);
- memcpy(dest + page.header_len, page.body, page.body_len);
-
- return nbytes;
-}
-
-static const char *
-vorbis_encoder_get_mime_type(G_GNUC_UNUSED struct encoder *_encoder)
-{
- return "audio/ogg";
-}
-
-const struct encoder_plugin vorbis_encoder_plugin = {
- .name = "vorbis",
- .init = vorbis_encoder_init,
- .finish = vorbis_encoder_finish,
- .open = vorbis_encoder_open,
- .close = vorbis_encoder_close,
- .end = vorbis_encoder_pre_tag,
- .flush = vorbis_encoder_flush,
- .pre_tag = vorbis_encoder_pre_tag,
- .tag = vorbis_encoder_tag,
- .write = vorbis_encoder_write,
- .read = vorbis_encoder_read,
- .get_mime_type = vorbis_encoder_get_mime_type,
-};
diff --git a/src/encoder/wave_encoder.c b/src/encoder/wave_encoder.c
deleted file mode 100644
index 9eeb4d513..000000000
--- a/src/encoder/wave_encoder.c
+++ /dev/null
@@ -1,278 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "encoder_api.h"
-#include "encoder_plugin.h"
-#include "fifo_buffer.h"
-#include "growing_fifo.h"
-
-#include <assert.h>
-#include <string.h>
-
-struct wave_encoder {
- struct encoder encoder;
- unsigned bits;
-
- struct fifo_buffer *buffer;
-};
-
-struct wave_header {
- uint32_t id_riff;
- uint32_t riff_size;
- uint32_t id_wave;
- uint32_t id_fmt;
- uint32_t fmt_size;
- uint16_t format;
- uint16_t channels;
- uint32_t freq;
- uint32_t byterate;
- uint16_t blocksize;
- uint16_t bits;
- uint32_t id_data;
- uint32_t data_size;
-};
-
-extern const struct encoder_plugin wave_encoder_plugin;
-
-static inline GQuark
-wave_encoder_quark(void)
-{
- return g_quark_from_static_string("wave_encoder");
-}
-
-static void
-fill_wave_header(struct wave_header *header, int channels, int bits,
- int freq, int block_size)
-{
- int data_size = 0x0FFFFFFF;
-
- /* constants */
- header->id_riff = GUINT32_TO_LE(0x46464952);
- header->id_wave = GUINT32_TO_LE(0x45564157);
- header->id_fmt = GUINT32_TO_LE(0x20746d66);
- header->id_data = GUINT32_TO_LE(0x61746164);
-
- /* wave format */
- header->format = GUINT16_TO_LE(1); // PCM_FORMAT
- header->channels = GUINT16_TO_LE(channels);
- header->bits = GUINT16_TO_LE(bits);
- header->freq = GUINT32_TO_LE(freq);
- header->blocksize = GUINT16_TO_LE(block_size);
- header->byterate = GUINT32_TO_LE(freq * block_size);
-
- /* chunk sizes (fake data length) */
- header->fmt_size = GUINT32_TO_LE(16);
- header->data_size = GUINT32_TO_LE(data_size);
- header->riff_size = GUINT32_TO_LE(4 + (8 + 16) +
- (8 + data_size));
-}
-
-static struct encoder *
-wave_encoder_init(G_GNUC_UNUSED const struct config_param *param,
- G_GNUC_UNUSED GError **error)
-{
- struct wave_encoder *encoder;
-
- encoder = g_new(struct wave_encoder, 1);
- encoder_struct_init(&encoder->encoder, &wave_encoder_plugin);
-
- return &encoder->encoder;
-}
-
-static void
-wave_encoder_finish(struct encoder *_encoder)
-{
- struct wave_encoder *encoder = (struct wave_encoder *)_encoder;
-
- g_free(encoder);
-}
-
-static bool
-wave_encoder_open(struct encoder *_encoder,
- G_GNUC_UNUSED struct audio_format *audio_format,
- G_GNUC_UNUSED GError **error)
-{
- struct wave_encoder *encoder = (struct wave_encoder *)_encoder;
-
- assert(audio_format_valid(audio_format));
-
- switch (audio_format->format) {
- case SAMPLE_FORMAT_S8:
- encoder->bits = 8;
- break;
-
- case SAMPLE_FORMAT_S16:
- encoder->bits = 16;
- break;
-
- case SAMPLE_FORMAT_S24_P32:
- encoder->bits = 24;
- break;
-
- case SAMPLE_FORMAT_S32:
- encoder->bits = 32;
- break;
-
- default:
- audio_format->format = SAMPLE_FORMAT_S16;
- encoder->bits = 16;
- break;
- }
-
- encoder->buffer = growing_fifo_new();
- struct wave_header *header =
- growing_fifo_write(&encoder->buffer, sizeof(*header));
-
- /* create PCM wave header in initial buffer */
- fill_wave_header(header,
- audio_format->channels,
- encoder->bits,
- audio_format->sample_rate,
- (encoder->bits / 8) * audio_format->channels );
- fifo_buffer_append(encoder->buffer, sizeof(*header));
-
- return true;
-}
-
-static void
-wave_encoder_close(struct encoder *_encoder)
-{
- struct wave_encoder *encoder = (struct wave_encoder *)_encoder;
-
- fifo_buffer_free(encoder->buffer);
-}
-
-static inline size_t
-pcm16_to_wave(uint16_t *dst16, const uint16_t *src16, size_t length)
-{
- size_t cnt = length >> 1;
- while (cnt > 0) {
- *dst16++ = GUINT16_TO_LE(*src16++);
- cnt--;
- }
- return length;
-}
-
-static inline size_t
-pcm32_to_wave(uint32_t *dst32, const uint32_t *src32, size_t length)
-{
- size_t cnt = length >> 2;
- while (cnt > 0){
- *dst32++ = GUINT32_TO_LE(*src32++);
- cnt--;
- }
- return length;
-}
-
-static inline size_t
-pcm24_to_wave(uint8_t *dst8, const uint32_t *src32, size_t length)
-{
- uint32_t value;
- uint8_t *dst_old = dst8;
-
- length = length >> 2;
- while (length > 0){
- value = *src32++;
- *dst8++ = (value) & 0xFF;
- *dst8++ = (value >> 8) & 0xFF;
- *dst8++ = (value >> 16) & 0xFF;
- length--;
- }
- //correct buffer length
- return (dst8 - dst_old);
-}
-
-static bool
-wave_encoder_write(struct encoder *_encoder,
- const void *src, size_t length,
- G_GNUC_UNUSED GError **error)
-{
- struct wave_encoder *encoder = (struct wave_encoder *)_encoder;
-
- void *dst = growing_fifo_write(&encoder->buffer, length);
-
-#if (G_BYTE_ORDER == G_LITTLE_ENDIAN)
- switch (encoder->bits) {
- case 8:
- case 16:
- case 32:// optimized cases
- memcpy(dst, src, length);
- break;
- case 24:
- length = pcm24_to_wave(dst, src, length);
- break;
- }
-#elif (G_BYTE_ORDER == G_BIG_ENDIAN)
- switch (encoder->bits) {
- case 8:
- memcpy(dst, src, length);
- break;
- case 16:
- length = pcm16_to_wave(dst, src, length);
- break;
- case 24:
- length = pcm24_to_wave(dst, src, length);
- break;
- case 32:
- length = pcm32_to_wave(dst, src, length);
- break;
- }
-#else
-#error G_BYTE_ORDER set to G_PDP_ENDIAN is not supported by wave_encoder
-#endif
-
- fifo_buffer_append(encoder->buffer, length);
- return true;
-}
-
-static size_t
-wave_encoder_read(struct encoder *_encoder, void *dest, size_t length)
-{
- struct wave_encoder *encoder = (struct wave_encoder *)_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;
-}
-
-static const char *
-wave_encoder_get_mime_type(G_GNUC_UNUSED struct encoder *_encoder)
-{
- return "audio/wav";
-}
-
-const struct encoder_plugin wave_encoder_plugin = {
- .name = "wave",
- .init = wave_encoder_init,
- .finish = wave_encoder_finish,
- .open = wave_encoder_open,
- .close = wave_encoder_close,
- .write = wave_encoder_write,
- .read = wave_encoder_read,
- .get_mime_type = wave_encoder_get_mime_type,
-};
diff --git a/src/encoder_api.h b/src/encoder_api.h
deleted file mode 100644
index 46c8d10c8..000000000
--- a/src/encoder_api.h
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * This header is included by encoder plugins.
- *
- */
-
-#ifndef MPD_ENCODER_API_H
-#define MPD_ENCODER_API_H
-
-#include "encoder_plugin.h"
-#include "audio_format.h"
-#include "tag.h"
-#include "conf.h"
-
-#endif
diff --git a/src/encoder_list.c b/src/encoder_list.c
deleted file mode 100644
index 2326c1099..000000000
--- a/src/encoder_list.c
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "encoder_list.h"
-#include "encoder_plugin.h"
-
-#include <string.h>
-
-extern const struct encoder_plugin null_encoder_plugin;
-extern const struct encoder_plugin vorbis_encoder_plugin;
-extern const struct encoder_plugin lame_encoder_plugin;
-extern const struct encoder_plugin twolame_encoder_plugin;
-extern const struct encoder_plugin wave_encoder_plugin;
-extern const struct encoder_plugin flac_encoder_plugin;
-
-const struct encoder_plugin *const encoder_plugins[] = {
- &null_encoder_plugin,
-#ifdef ENABLE_VORBIS_ENCODER
- &vorbis_encoder_plugin,
-#endif
-#ifdef ENABLE_LAME_ENCODER
- &lame_encoder_plugin,
-#endif
-#ifdef ENABLE_TWOLAME_ENCODER
- &twolame_encoder_plugin,
-#endif
-#ifdef ENABLE_WAVE_ENCODER
- &wave_encoder_plugin,
-#endif
-#ifdef ENABLE_FLAC_ENCODER
- &flac_encoder_plugin,
-#endif
- NULL
-};
-
-const struct encoder_plugin *
-encoder_plugin_get(const char *name)
-{
- encoder_plugins_for_each(plugin)
- if (strcmp(plugin->name, name) == 0)
- return plugin;
-
- return NULL;
-}
diff --git a/src/encoder_list.h b/src/encoder_list.h
deleted file mode 100644
index fb1c9bf9c..000000000
--- a/src/encoder_list.h
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * 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_ENCODER_LIST_H
-#define MPD_ENCODER_LIST_H
-
-struct encoder_plugin;
-
-extern const struct encoder_plugin *const encoder_plugins[];
-
-#define encoder_plugins_for_each(plugin) \
- for (const struct encoder_plugin *plugin, \
- *const*encoder_plugin_iterator = &encoder_plugins[0]; \
- (plugin = *encoder_plugin_iterator) != NULL; \
- ++encoder_plugin_iterator)
-
-/**
- * Looks up an encoder plugin by its name.
- *
- * @param name the encoder name to look for
- * @return the encoder plugin with the specified name, or NULL if none
- * was found
- */
-const struct encoder_plugin *
-encoder_plugin_get(const char *name);
-
-#endif
diff --git a/src/encoder_plugin.h b/src/encoder_plugin.h
deleted file mode 100644
index 3a42d79f4..000000000
--- a/src/encoder_plugin.h
+++ /dev/null
@@ -1,336 +0,0 @@
-/*
- * 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_ENCODER_PLUGIN_H
-#define MPD_ENCODER_PLUGIN_H
-
-#include <glib.h>
-
-#include <assert.h>
-#include <stdbool.h>
-#include <stddef.h>
-
-struct encoder_plugin;
-struct audio_format;
-struct config_param;
-struct tag;
-
-struct encoder {
- const struct encoder_plugin *plugin;
-
-#ifndef NDEBUG
- bool open, pre_tag, tag, end;
-#endif
-};
-
-struct encoder_plugin {
- const char *name;
-
- struct encoder *(*init)(const struct config_param *param,
- GError **error);
-
- void (*finish)(struct encoder *encoder);
-
- bool (*open)(struct encoder *encoder,
- struct audio_format *audio_format,
- GError **error);
-
- void (*close)(struct encoder *encoder);
-
- bool (*end)(struct encoder *encoder, GError **error);
-
- bool (*flush)(struct encoder *encoder, GError **error);
-
- bool (*pre_tag)(struct encoder *encoder, GError **error);
-
- bool (*tag)(struct encoder *encoder, const struct tag *tag,
- GError **error);
-
- bool (*write)(struct encoder *encoder,
- const void *data, size_t length,
- GError **error);
-
- size_t (*read)(struct encoder *encoder, void *dest, size_t length);
-
- const char *(*get_mime_type)(struct encoder *encoder);
-};
-
-/**
- * Initializes an encoder object. This should be used by encoder
- * plugins to initialize their base class.
- */
-static inline void
-encoder_struct_init(struct encoder *encoder,
- const struct encoder_plugin *plugin)
-{
- encoder->plugin = plugin;
-
-#ifndef NDEBUG
- encoder->open = false;
-#endif
-}
-
-/**
- * Creates a new encoder object.
- *
- * @param plugin the encoder plugin
- * @param param optional configuration
- * @param error location to store the error occurring, or NULL to ignore errors.
- * @return an encoder object on success, NULL on failure
- */
-static inline struct encoder *
-encoder_init(const struct encoder_plugin *plugin,
- const struct config_param *param, GError **error)
-{
- return plugin->init(param, error);
-}
-
-/**
- * Frees an encoder object.
- *
- * @param encoder the encoder
- */
-static inline void
-encoder_finish(struct encoder *encoder)
-{
- assert(!encoder->open);
-
- encoder->plugin->finish(encoder);
-}
-
-/**
- * Opens an encoder object. You must call this prior to using it.
- * Before you free it, you must call encoder_close(). You may open
- * and close (reuse) one encoder any number of times.
- *
- * After this function returns successfully and before the first
- * encoder_write() call, you should invoke encoder_read() to obtain
- * the file header.
- *
- * @param encoder the encoder
- * @param audio_format the encoder's input audio format; the plugin
- * may modify the struct to adapt it to its abilities
- * @param error location to store the error occurring, or NULL to ignore errors.
- * @return true on success
- */
-static inline bool
-encoder_open(struct encoder *encoder, struct audio_format *audio_format,
- GError **error)
-{
- assert(!encoder->open);
-
- bool success = encoder->plugin->open(encoder, audio_format, error);
-#ifndef NDEBUG
- encoder->open = success;
- encoder->pre_tag = encoder->tag = encoder->end = false;
-#endif
- return success;
-}
-
-/**
- * Closes an encoder object. This disables the encoder, and readies
- * it for reusal by calling encoder_open() again.
- *
- * @param encoder the encoder
- */
-static inline void
-encoder_close(struct encoder *encoder)
-{
- assert(encoder->open);
-
- if (encoder->plugin->close != NULL)
- encoder->plugin->close(encoder);
-
-#ifndef NDEBUG
- encoder->open = false;
-#endif
-}
-
-/**
- * Ends the stream: flushes the encoder object, generate an
- * end-of-stream marker (if applicable), make everything which might
- * currently be buffered available by encoder_read().
- *
- * After this function has been called, the encoder may not be usable
- * for more data, and only encoder_read() and encoder_close() can be
- * called.
- *
- * @param encoder the encoder
- * @param error location to store the error occuring, or NULL to ignore errors.
- * @return true on success
- */
-static inline bool
-encoder_end(struct encoder *encoder, GError **error)
-{
- assert(encoder->open);
- assert(!encoder->end);
-
-#ifndef NDEBUG
- encoder->end = true;
-#endif
-
- /* this method is optional */
- return encoder->plugin->end != NULL
- ? encoder->plugin->end(encoder, error)
- : true;
-}
-
-/**
- * Flushes an encoder object, make everything which might currently be
- * buffered available by encoder_read().
- *
- * @param encoder the encoder
- * @param error location to store the error occurring, or NULL to ignore errors.
- * @return true on success
- */
-static inline bool
-encoder_flush(struct encoder *encoder, GError **error)
-{
- assert(encoder->open);
- assert(!encoder->pre_tag);
- assert(!encoder->tag);
- assert(!encoder->end);
-
- /* this method is optional */
- return encoder->plugin->flush != NULL
- ? encoder->plugin->flush(encoder, error)
- : true;
-}
-
-/**
- * Prepare for sending a tag to the encoder. This is used by some
- * encoders to flush the previous sub-stream, in preparation to begin
- * a new one.
- *
- * @param encoder the encoder
- * @param tag the tag object
- * @param error location to store the error occuring, or NULL to ignore errors.
- * @return true on success
- */
-static inline bool
-encoder_pre_tag(struct encoder *encoder, GError **error)
-{
- assert(encoder->open);
- assert(!encoder->pre_tag);
- assert(!encoder->tag);
- assert(!encoder->end);
-
- /* this method is optional */
- bool success = encoder->plugin->pre_tag != NULL
- ? encoder->plugin->pre_tag(encoder, error)
- : true;
-
-#ifndef NDEBUG
- encoder->pre_tag = success;
-#endif
- return success;
-}
-
-/**
- * Sends a tag to the encoder.
- *
- * Instructions: call encoder_pre_tag(); then obtain flushed data with
- * encoder_read(); finally call encoder_tag().
- *
- * @param encoder the encoder
- * @param tag the tag object
- * @param error location to store the error occurring, or NULL to ignore errors.
- * @return true on success
- */
-static inline bool
-encoder_tag(struct encoder *encoder, const struct tag *tag, GError **error)
-{
- assert(encoder->open);
- assert(!encoder->pre_tag);
- assert(encoder->tag);
- assert(!encoder->end);
-
-#ifndef NDEBUG
- encoder->tag = false;
-#endif
-
- /* this method is optional */
- return encoder->plugin->tag != NULL
- ? encoder->plugin->tag(encoder, tag, error)
- : true;
-}
-
-/**
- * Writes raw PCM data to the encoder.
- *
- * @param encoder the encoder
- * @param data the buffer containing PCM samples
- * @param length the length of the buffer in bytes
- * @param error location to store the error occurring, or NULL to ignore errors.
- * @return true on success
- */
-static inline bool
-encoder_write(struct encoder *encoder, const void *data, size_t length,
- GError **error)
-{
- assert(encoder->open);
- assert(!encoder->pre_tag);
- assert(!encoder->tag);
- assert(!encoder->end);
-
- return encoder->plugin->write(encoder, data, length, error);
-}
-
-/**
- * Reads encoded data from the encoder.
- *
- * Call this repeatedly until no more data is returned.
- *
- * @param encoder the encoder
- * @param dest the destination buffer to copy to
- * @param length the maximum length of the destination buffer
- * @return the number of bytes written to #dest
- */
-static inline size_t
-encoder_read(struct encoder *encoder, void *dest, size_t length)
-{
- assert(encoder->open);
- assert(!encoder->pre_tag || !encoder->tag);
-
-#ifndef NDEBUG
- if (encoder->pre_tag) {
- encoder->pre_tag = false;
- encoder->tag = true;
- }
-#endif
-
- return encoder->plugin->read(encoder, dest, length);
-}
-
-/**
- * Get mime type of encoded content.
- *
- * @param plugin the encoder plugin
- * @return an constant string, NULL on failure
- */
-static inline const char *
-encoder_get_mime_type(struct encoder *encoder)
-{
- /* this method is optional */
- return encoder->plugin->get_mime_type != NULL
- ? encoder->plugin->get_mime_type(encoder)
- : NULL;
-}
-
-#endif
diff --git a/src/event/BufferedSocket.cxx b/src/event/BufferedSocket.cxx
new file mode 100644
index 000000000..05e703441
--- /dev/null
+++ b/src/event/BufferedSocket.cxx
@@ -0,0 +1,150 @@
+/*
+ * 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 "BufferedSocket.hxx"
+#include "SocketError.hxx"
+#include "util/fifo_buffer.h"
+
+#include <assert.h>
+#include <stdint.h>
+#include <string.h>
+
+BufferedSocket::~BufferedSocket()
+{
+ if (input != nullptr)
+ fifo_buffer_free(input);
+}
+
+BufferedSocket::ssize_t
+BufferedSocket::DirectRead(void *data, size_t length)
+{
+ const auto nbytes = SocketMonitor::Read((char *)data, length);
+ if (gcc_likely(nbytes > 0))
+ return nbytes;
+
+ if (nbytes == 0) {
+ OnSocketClosed();
+ return -1;
+ }
+
+ const auto code = GetSocketError();
+ if (IsSocketErrorAgain(code))
+ return 0;
+
+ if (IsSocketErrorClosed(code))
+ OnSocketClosed();
+ else
+ OnSocketError(NewSocketError(code));
+ return -1;
+}
+
+bool
+BufferedSocket::ReadToBuffer()
+{
+ assert(IsDefined());
+
+ if (input == nullptr)
+ input = fifo_buffer_new(8192);
+
+ size_t length;
+ void *buffer = fifo_buffer_write(input, &length);
+ assert(buffer != nullptr);
+
+ const auto nbytes = DirectRead(buffer, length);
+ if (nbytes > 0)
+ fifo_buffer_append(input, nbytes);
+
+ return nbytes >= 0;
+}
+
+bool
+BufferedSocket::ResumeInput()
+{
+ assert(IsDefined());
+
+ if (input == nullptr) {
+ ScheduleRead();
+ return true;
+ }
+
+ while (true) {
+ size_t length;
+ const void *data = fifo_buffer_read(input, &length);
+ if (data == nullptr) {
+ ScheduleRead();
+ return true;
+ }
+
+ const auto result = OnSocketInput(data, length);
+ switch (result) {
+ case InputResult::MORE:
+ if (fifo_buffer_is_full(input)) {
+ // TODO
+ OnSocketError(g_error_new_literal(g_quark_from_static_string("buffered_socket"),
+ 0, "Input buffer is full"));
+ return false;
+ }
+
+ ScheduleRead();
+ return true;
+
+ case InputResult::PAUSE:
+ CancelRead();
+ return true;
+
+ case InputResult::AGAIN:
+ continue;
+
+ case InputResult::CLOSED:
+ return false;
+ }
+ }
+}
+
+void
+BufferedSocket::ConsumeInput(size_t nbytes)
+{
+ assert(IsDefined());
+
+ fifo_buffer_consume(input, nbytes);
+}
+
+bool
+BufferedSocket::OnSocketReady(unsigned flags)
+{
+ assert(IsDefined());
+
+ if (gcc_unlikely(flags & (ERROR|HANGUP))) {
+ OnSocketClosed();
+ return false;
+ }
+
+ if (flags & READ) {
+ assert(input == nullptr || !fifo_buffer_is_full(input));
+
+ if (!ReadToBuffer() || !ResumeInput())
+ return false;
+
+ if (input == nullptr || !fifo_buffer_is_full(input))
+ ScheduleRead();
+ }
+
+ return true;
+}
diff --git a/src/event/BufferedSocket.hxx b/src/event/BufferedSocket.hxx
new file mode 100644
index 000000000..86deb8d98
--- /dev/null
+++ b/src/event/BufferedSocket.hxx
@@ -0,0 +1,104 @@
+/*
+ * 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_BUFFERED_SOCKET_HXX
+#define MPD_BUFFERED_SOCKET_HXX
+
+#include "check.h"
+#include "SocketMonitor.hxx"
+#include "gcc.h"
+
+struct fifo_buffer;
+
+/**
+ * A #SocketMonitor specialization that adds an input buffer.
+ */
+class BufferedSocket : protected SocketMonitor {
+ fifo_buffer *input;
+
+public:
+ BufferedSocket(int _fd, EventLoop &_loop)
+ :SocketMonitor(_fd, _loop), input(nullptr) {
+ ScheduleRead();
+ }
+
+ ~BufferedSocket();
+
+ using SocketMonitor::IsDefined;
+ using SocketMonitor::Close;
+ using SocketMonitor::Write;
+
+private:
+ ssize_t DirectRead(void *data, size_t length);
+
+ /**
+ * Receive data from the socket to the input buffer.
+ *
+ * @return false if the socket has been closed
+ */
+ bool ReadToBuffer();
+
+protected:
+ /**
+ * @return false if the socket has been closed
+ */
+ bool ResumeInput();
+
+ /**
+ * Mark a portion of the input buffer "consumed". Only
+ * allowed to be called from OnSocketInput(). This method
+ * does not invalidate the pointer passed to OnSocketInput()
+ * yet.
+ */
+ void ConsumeInput(size_t nbytes);
+
+ enum class InputResult {
+ /**
+ * The method was successful, and it is ready to
+ * read more data.
+ */
+ MORE,
+
+ /**
+ * The method does not want to get more data for now.
+ * It will call ResumeInput() when it's ready for
+ * more.
+ */
+ PAUSE,
+
+ /**
+ * The method wants to be called again immediately, if
+ * there's more data in the buffer.
+ */
+ AGAIN,
+
+ /**
+ * The method has closed the socket.
+ */
+ CLOSED,
+ };
+
+ virtual InputResult OnSocketInput(const void *data, size_t length) = 0;
+ virtual void OnSocketError(GError *error) = 0;
+ virtual void OnSocketClosed() = 0;
+
+ virtual bool OnSocketReady(unsigned flags) override;
+};
+
+#endif
diff --git a/src/event/FullyBufferedSocket.cxx b/src/event/FullyBufferedSocket.cxx
new file mode 100644
index 000000000..a92cb68aa
--- /dev/null
+++ b/src/event/FullyBufferedSocket.cxx
@@ -0,0 +1,132 @@
+/*
+ * 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 "FullyBufferedSocket.hxx"
+#include "SocketError.hxx"
+#include "util/fifo_buffer.h"
+
+#include <assert.h>
+#include <stdint.h>
+#include <string.h>
+
+#ifndef WIN32
+#include <sys/types.h>
+#include <sys/socket.h>
+#endif
+
+FullyBufferedSocket::ssize_t
+FullyBufferedSocket::DirectWrite(const void *data, size_t length)
+{
+ const auto nbytes = SocketMonitor::Write((const char *)data, length);
+ if (gcc_unlikely(nbytes < 0)) {
+ const auto code = GetSocketError();
+ if (IsSocketErrorAgain(code))
+ return 0;
+
+ Cancel();
+
+ if (IsSocketErrorClosed(code))
+ OnSocketClosed();
+ else
+ OnSocketError(NewSocketError(code));
+ }
+
+ return nbytes;
+}
+
+bool
+FullyBufferedSocket::WriteFromBuffer()
+{
+ assert(IsDefined());
+
+ size_t length;
+ const void *data = output.Read(&length);
+ if (data == nullptr) {
+ CancelWrite();
+ return true;
+ }
+
+ auto nbytes = DirectWrite(data, length);
+ if (gcc_unlikely(nbytes <= 0))
+ return nbytes == 0;
+
+ output.Consume(nbytes);
+
+ if (output.IsEmpty())
+ CancelWrite();
+
+ return true;
+}
+
+bool
+FullyBufferedSocket::Write(const void *data, size_t length)
+{
+ assert(IsDefined());
+
+#if 0
+ /* TODO: disabled because this would add overhead on some callers (the ones that often), but it may be useful */
+
+ if (output.IsEmpty()) {
+ /* try to write it directly first */
+ const auto nbytes = DirectWrite(data, length);
+ if (gcc_likely(nbytes > 0)) {
+ data = (const uint8_t *)data + nbytes;
+ length -= nbytes;
+ if (length == 0)
+ return true;
+ } else if (nbytes < 0)
+ return false;
+ }
+#endif
+
+ if (!output.Append(data, length)) {
+ // TODO
+ OnSocketError(g_error_new_literal(g_quark_from_static_string("buffered_socket"),
+ 0, "Output buffer is full"));
+ return false;
+ }
+
+ ScheduleWrite();
+ return true;
+}
+
+bool
+FullyBufferedSocket::OnSocketReady(unsigned flags)
+{
+ const bool was_empty = output.IsEmpty();
+ if (!BufferedSocket::OnSocketReady(flags))
+ return false;
+
+ if (was_empty && !output.IsEmpty())
+ /* just in case the OnSocketInput() method has added
+ data to the output buffer: try to send it now
+ instead of waiting for the next event loop
+ iteration */
+ flags |= WRITE;
+
+ if (flags & WRITE) {
+ assert(!output.IsEmpty());
+
+ if (!WriteFromBuffer())
+ return false;
+ }
+
+ return true;
+}
diff --git a/src/event/FullyBufferedSocket.hxx b/src/event/FullyBufferedSocket.hxx
new file mode 100644
index 000000000..c67c2c78d
--- /dev/null
+++ b/src/event/FullyBufferedSocket.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_FULLY_BUFFERED_SOCKET_HXX
+#define MPD_FULLY_BUFFERED_SOCKET_HXX
+
+#include "check.h"
+#include "BufferedSocket.hxx"
+#include "util/PeakBuffer.hxx"
+#include "gcc.h"
+
+/**
+ * A #BufferedSocket specialization that adds an output buffer.
+ */
+class FullyBufferedSocket : protected BufferedSocket {
+ PeakBuffer output;
+
+public:
+ FullyBufferedSocket(int _fd, EventLoop &_loop,
+ size_t normal_size, size_t peak_size=0)
+ :BufferedSocket(_fd, _loop),
+ output(normal_size, peak_size) {
+ }
+
+ using BufferedSocket::IsDefined;
+ using BufferedSocket::Close;
+
+private:
+ ssize_t DirectWrite(const void *data, size_t length);
+
+ /**
+ * Send data from the output buffer to the socket.
+ *
+ * @return false if the socket has been closed
+ */
+ bool WriteFromBuffer();
+
+protected:
+ /**
+ * @return false if the socket has been closed
+ */
+ bool Write(const void *data, size_t length);
+
+ virtual bool OnSocketReady(unsigned flags) override;
+};
+
+#endif
diff --git a/src/event/Loop.hxx b/src/event/Loop.hxx
new file mode 100644
index 000000000..72731ea2b
--- /dev/null
+++ b/src/event/Loop.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_EVENT_LOOP_HXX
+#define MPD_EVENT_LOOP_HXX
+
+#include "check.h"
+#include "gcc.h"
+
+#include <glib.h>
+
+class EventLoop {
+ GMainContext *context;
+ GMainLoop *loop;
+
+public:
+ EventLoop()
+ :context(g_main_context_new()),
+ loop(g_main_loop_new(context, false)) {}
+
+ struct Default {};
+ EventLoop(gcc_unused Default _dummy)
+ :context(g_main_context_ref(g_main_context_default())),
+ loop(g_main_loop_new(context, false)) {}
+
+ ~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() {
+ g_main_loop_run(loop);
+ }
+
+ guint AddIdle(GSourceFunc function, gpointer data) {
+ GSource *source = g_idle_source_new();
+ g_source_set_callback(source, function, data, NULL);
+ guint id = g_source_attach(source, GetContext());
+ g_source_unref(source);
+ return id;
+ }
+
+ GSource *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;
+ }
+
+ GSource *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;
+ }
+};
+
+#endif /* MAIN_NOTIFY_H */
diff --git a/src/event/MultiSocketMonitor.cxx b/src/event/MultiSocketMonitor.cxx
new file mode 100644
index 000000000..6f20b907c
--- /dev/null
+++ b/src/event/MultiSocketMonitor.cxx
@@ -0,0 +1,107 @@
+/*
+ * 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 "MultiSocketMonitor.hxx"
+#include "Loop.hxx"
+#include "fd_util.h"
+#include "gcc.h"
+
+#include <assert.h>
+
+/**
+ * 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))) {
+ 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::Check() const
+{
+ if (CheckSockets())
+ return true;
+
+ for (const auto &i : fds)
+ if (i.revents != 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;
+}
diff --git a/src/event/MultiSocketMonitor.hxx b/src/event/MultiSocketMonitor.hxx
new file mode 100644
index 000000000..bf0a221a2
--- /dev/null
+++ b/src/event/MultiSocketMonitor.hxx
@@ -0,0 +1,125 @@
+/*
+ * 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_MULTI_SOCKET_MONITOR_HXX
+#define MPD_MULTI_SOCKET_MONITOR_HXX
+
+#include "check.h"
+#include "gcc.h"
+#include "glib_compat.h"
+
+#include <glib.h>
+
+#include <forward_list>
+
+#include <assert.h>
+
+#ifdef WIN32
+/* ERRORis a WIN32 macro that poisons our namespace; this is a
+ kludge to allow us to use it anyway */
+#ifdef ERROR
+#undef ERROR
+#endif
+#endif
+
+class EventLoop;
+
+/**
+ * Monitor multiple sockets.
+ */
+class MultiSocketMonitor {
+ struct Source {
+ GSource base;
+
+ MultiSocketMonitor *monitor;
+ };
+
+ EventLoop &loop;
+ Source *source;
+ std::forward_list<GPollFD> fds;
+
+public:
+ 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;
+
+ MultiSocketMonitor(EventLoop &_loop);
+ ~MultiSocketMonitor();
+
+public:
+ gcc_pure
+ gint64 GetTime() const {
+ return g_source_get_time(&source->base);
+ }
+
+ void InvalidateSockets() {
+ /* no-op because GLib always calls the GSource's
+ "prepare" method before each poll() anyway */
+ }
+
+ void AddSocket(int fd, unsigned events) {
+ fds.push_front({fd, gushort(events), 0});
+ g_source_add_poll(&source->base, &fds.front());
+ }
+
+ template<typename E>
+ void UpdateSocketList(E &&e) {
+ for (auto prev = fds.before_begin(), end = fds.end(),
+ i = std::next(prev);
+ i != end; i = std::next(prev)) {
+ assert(i->events != 0);
+
+ unsigned events = e(i->fd);
+ if (events != 0) {
+ i->events = events;
+ prev = i;
+ } else {
+ g_source_remove_poll(&source->base, &*i);
+ fds.erase_after(prev);
+ }
+ }
+ }
+
+protected:
+ virtual void PrepareSockets(gcc_unused gint *timeout_r) {}
+ virtual bool CheckSockets() const { return false; }
+ virtual void DispatchSockets() = 0;
+
+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) {
+ PrepareSockets(timeout_r);
+ return false;
+ }
+
+ bool Check() const;
+
+ void Dispatch() {
+ DispatchSockets();
+ }
+};
+
+#endif
diff --git a/src/event/ServerSocket.cxx b/src/event/ServerSocket.cxx
new file mode 100644
index 000000000..119bfe1d7
--- /dev/null
+++ b/src/event/ServerSocket.cxx
@@ -0,0 +1,438 @@
+/*
+ * 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 HAVE_STRUCT_UCRED
+#define _GNU_SOURCE 1
+#endif
+
+#include "ServerSocket.hxx"
+#include "SocketUtil.hxx"
+#include "SocketError.hxx"
+#include "event/SocketMonitor.hxx"
+#include "resolver.h"
+#include "fd_util.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#ifdef WIN32
+#include <ws2tcpip.h>
+#include <winsock.h>
+#else
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netdb.h>
+#endif
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "listen"
+
+#define DEFAULT_PORT 6600
+
+class OneServerSocket final : private SocketMonitor {
+ ServerSocket &parent;
+
+ const unsigned serial;
+
+ char *path;
+
+ size_t address_length;
+ struct sockaddr *address;
+
+public:
+ OneServerSocket(EventLoop &_loop, ServerSocket &_parent,
+ unsigned _serial,
+ const struct sockaddr *_address,
+ size_t _address_length)
+ :SocketMonitor(_loop),
+ parent(_parent), serial(_serial),
+ path(nullptr),
+ address_length(_address_length),
+ address((sockaddr *)g_memdup(_address, _address_length))
+ {
+ assert(_address != nullptr);
+ assert(_address_length > 0);
+ }
+
+ OneServerSocket(const OneServerSocket &other) = delete;
+ OneServerSocket &operator=(const OneServerSocket &other) = delete;
+
+ ~OneServerSocket() {
+ g_free(path);
+ g_free(address);
+ }
+
+ unsigned GetSerial() const {
+ return serial;
+ }
+
+ void SetPath(const char *_path) {
+ assert(path == nullptr);
+
+ path = g_strdup(_path);
+ }
+
+ bool Open(GError **error_r);
+
+ using SocketMonitor::IsDefined;
+ using SocketMonitor::Close;
+
+ char *ToString() const;
+
+ void SetFD(int _fd) {
+ SocketMonitor::Open(_fd);
+ SocketMonitor::ScheduleRead();
+ }
+
+ void Accept();
+
+private:
+ virtual bool OnSocketReady(unsigned flags) override;
+};
+
+static GQuark
+server_socket_quark(void)
+{
+ return g_quark_from_static_string("server_socket");
+}
+
+/**
+ * Wraper for sockaddr_to_string() which never fails.
+ */
+char *
+OneServerSocket::ToString() const
+{
+ char *p = sockaddr_to_string(address, address_length, nullptr);
+ if (p == nullptr)
+ p = g_strdup("[unknown]");
+ return p;
+}
+
+static int
+get_remote_uid(int fd)
+{
+#ifdef HAVE_STRUCT_UCRED
+ struct ucred cred;
+ socklen_t len = sizeof (cred);
+
+ if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) < 0)
+ return 0;
+
+ return cred.uid;
+#else
+#ifdef HAVE_GETPEEREID
+ uid_t euid;
+ gid_t egid;
+
+ if (getpeereid(fd, &euid, &egid) == 0)
+ return euid;
+#else
+ (void)fd;
+#endif
+ return -1;
+#endif
+}
+
+inline void
+OneServerSocket::Accept()
+{
+ struct sockaddr_storage peer_address;
+ size_t peer_address_length = sizeof(peer_address);
+ int peer_fd =
+ accept_cloexec_nonblock(Get(), (struct sockaddr*)&peer_address,
+ &peer_address_length);
+ if (peer_fd < 0) {
+ const SocketErrorMessage msg;
+ g_warning("accept() failed: %s", (const char *)msg);
+ return;
+ }
+
+ if (socket_keepalive(peer_fd)) {
+ const SocketErrorMessage msg;
+ g_warning("Could not set TCP keepalive option: %s",
+ (const char *)msg);
+ }
+
+ parent.OnAccept(peer_fd,
+ (const sockaddr &)peer_address,
+ peer_address_length, get_remote_uid(peer_fd));
+}
+
+bool
+OneServerSocket::OnSocketReady(gcc_unused unsigned flags)
+{
+ Accept();
+ return true;
+}
+
+inline bool
+OneServerSocket::Open(GError **error_r)
+{
+ assert(!IsDefined());
+
+ int _fd = socket_bind_listen(address->sa_family,
+ SOCK_STREAM, 0,
+ address, address_length, 5,
+ error_r);
+ if (_fd < 0)
+ return false;
+
+ /* allow everybody to connect */
+
+ if (path != nullptr)
+ chmod(path, 0666);
+
+ /* register in the GLib main loop */
+
+ SetFD(_fd);
+
+ return true;
+}
+
+ServerSocket::ServerSocket(EventLoop &_loop)
+ :loop(_loop), next_serial(1) {}
+
+/* this is just here to allow the OneServerSocket forward
+ declaration */
+ServerSocket::~ServerSocket() {}
+
+bool
+ServerSocket::Open(GError **error_r)
+{
+ OneServerSocket *good = nullptr, *bad = nullptr;
+ GError *last_error = nullptr;
+
+ for (auto &i : sockets) {
+ assert(i.GetSerial() > 0);
+ assert(good == nullptr || i.GetSerial() <= good->GetSerial());
+
+ if (bad != nullptr && i.GetSerial() != bad->GetSerial()) {
+ Close();
+ g_propagate_error(error_r, last_error);
+ return false;
+ }
+
+ GError *error = nullptr;
+ if (!i.Open(&error)) {
+ if (good != nullptr && good->GetSerial() == i.GetSerial()) {
+ char *address_string = i.ToString();
+ char *good_string = good->ToString();
+ g_warning("bind to '%s' failed: %s "
+ "(continuing anyway, because "
+ "binding to '%s' succeeded)",
+ address_string, error->message,
+ good_string);
+ g_free(address_string);
+ g_free(good_string);
+ g_error_free(error);
+ } else if (bad == nullptr) {
+ bad = &i;
+
+ char *address_string = i.ToString();
+ g_propagate_prefixed_error(&last_error, error,
+ "Failed to bind to '%s': ",
+ address_string);
+ g_free(address_string);
+ } else
+ g_error_free(error);
+ continue;
+ }
+
+ /* mark this socket as "good", and clear previous
+ errors */
+
+ good = &i;
+
+ if (bad != nullptr) {
+ bad = nullptr;
+ g_error_free(last_error);
+ last_error = nullptr;
+ }
+ }
+
+ if (bad != nullptr) {
+ Close();
+ g_propagate_error(error_r, last_error);
+ return false;
+ }
+
+ return true;
+}
+
+void
+ServerSocket::Close()
+{
+ for (auto &i : sockets)
+ if (i.IsDefined())
+ i.Close();
+}
+
+OneServerSocket &
+ServerSocket::AddAddress(const sockaddr &address, size_t address_length)
+{
+ sockets.emplace_front(loop, *this, next_serial,
+ &address, address_length);
+
+ return sockets.front();
+}
+
+bool
+ServerSocket::AddFD(int fd, GError **error_r)
+{
+ assert(fd >= 0);
+
+ struct sockaddr_storage address;
+ socklen_t address_length = sizeof(address);
+ if (getsockname(fd, (struct sockaddr *)&address,
+ &address_length) < 0) {
+ SetSocketError(error_r);
+ g_prefix_error(error_r, "Failed to get socket address");
+ return false;
+ }
+
+ OneServerSocket &s = AddAddress((const sockaddr &)address,
+ address_length);
+ s.SetFD(fd);
+
+ return true;
+}
+
+#ifdef HAVE_TCP
+
+inline void
+ServerSocket::AddPortIPv4(unsigned port)
+{
+ struct sockaddr_in sin;
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_port = htons(port);
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = INADDR_ANY;
+
+ AddAddress((const sockaddr &)sin, sizeof(sin));
+}
+
+#ifdef HAVE_IPV6
+inline void
+ServerSocket::AddPortIPv6(unsigned port)
+{
+ struct sockaddr_in6 sin;
+ memset(&sin, 0, sizeof(sin));
+ sin.sin6_port = htons(port);
+ sin.sin6_family = AF_INET6;
+
+ AddAddress((const sockaddr &)sin, sizeof(sin));
+}
+#endif /* HAVE_IPV6 */
+
+#endif /* HAVE_TCP */
+
+bool
+ServerSocket::AddPort(unsigned port, GError **error_r)
+{
+#ifdef HAVE_TCP
+ if (port == 0 || port > 0xffff) {
+ g_set_error(error_r, server_socket_quark(), 0,
+ "Invalid TCP port");
+ return false;
+ }
+
+#ifdef HAVE_IPV6
+ AddPortIPv6(port);
+#endif
+ AddPortIPv4(port);
+
+ ++next_serial;
+
+ return true;
+#else /* HAVE_TCP */
+ (void)port;
+
+ g_set_error(error_r, server_socket_quark(), 0,
+ "TCP support is disabled");
+ return false;
+#endif /* HAVE_TCP */
+}
+
+bool
+ServerSocket::AddHost(const char *hostname, unsigned port, GError **error_r)
+{
+#ifdef HAVE_TCP
+ struct addrinfo *ai = resolve_host_port(hostname, port,
+ AI_PASSIVE, SOCK_STREAM,
+ error_r);
+ if (ai == nullptr)
+ return false;
+
+ for (const struct addrinfo *i = ai; i != nullptr; i = i->ai_next)
+ AddAddress(*i->ai_addr, i->ai_addrlen);
+
+ freeaddrinfo(ai);
+
+ ++next_serial;
+
+ return true;
+#else /* HAVE_TCP */
+ (void)hostname;
+ (void)port;
+
+ g_set_error(error_r, server_socket_quark(), 0,
+ "TCP support is disabled");
+ return false;
+#endif /* HAVE_TCP */
+}
+
+bool
+ServerSocket::AddPath(const char *path, GError **error_r)
+{
+#ifdef HAVE_UN
+ struct sockaddr_un s_un;
+
+ size_t path_length = strlen(path);
+ if (path_length >= sizeof(s_un.sun_path)) {
+ g_set_error(error_r, server_socket_quark(), 0,
+ "UNIX socket path is too long");
+ return false;
+ }
+
+ unlink(path);
+
+ s_un.sun_family = AF_UNIX;
+ memcpy(s_un.sun_path, path, path_length + 1);
+
+ OneServerSocket &s = AddAddress((const sockaddr &)s_un, sizeof(s_un));
+ s.SetPath(path);
+
+ return true;
+#else /* !HAVE_UN */
+ (void)path;
+
+ g_set_error(error_r, server_socket_quark(), 0,
+ "UNIX domain socket support is disabled");
+ return false;
+#endif /* !HAVE_UN */
+}
+
diff --git a/src/event/ServerSocket.hxx b/src/event/ServerSocket.hxx
new file mode 100644
index 000000000..600cdf8a7
--- /dev/null
+++ b/src/event/ServerSocket.hxx
@@ -0,0 +1,121 @@
+/*
+ * 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_SERVER_SOCKET_HXX
+#define MPD_SERVER_SOCKET_HXX
+
+#include "gerror.h"
+
+#include <forward_list>
+
+#include <stddef.h>
+
+struct sockaddr;
+class EventLoop;
+
+typedef void (*server_socket_callback_t)(int fd,
+ const struct sockaddr *address,
+ size_t address_length, int uid,
+ void *ctx);
+
+class OneServerSocket;
+
+class ServerSocket {
+ friend class OneServerSocket;
+
+ EventLoop &loop;
+
+ std::forward_list<OneServerSocket> sockets;
+
+ unsigned next_serial;
+
+public:
+ ServerSocket(EventLoop &_loop);
+ ~ServerSocket();
+
+ EventLoop &GetEventLoop() {
+ return loop;
+ }
+
+private:
+ OneServerSocket &AddAddress(const sockaddr &address, size_t length);
+
+ /**
+ * Add a listener on a port on all IPv4 interfaces.
+ *
+ * @param port the TCP port
+ */
+ void AddPortIPv4(unsigned port);
+
+ /**
+ * Add a listener on a port on all IPv6 interfaces.
+ *
+ * @param port the TCP port
+ */
+ void AddPortIPv6(unsigned port);
+
+public:
+ /**
+ * Add a listener on a port on all interfaces.
+ *
+ * @param port the TCP port
+ * @param error_r location to store the error occurring, or NULL to
+ * ignore errors
+ * @return true on success
+ */
+ bool AddPort(unsigned port, GError **error_r);
+
+ /**
+ * Resolves a host name, and adds listeners on all addresses in the
+ * result set.
+ *
+ * @param hostname the host name to be resolved
+ * @param port the TCP port
+ * @param error_r location to store the error occurring, or NULL to
+ * ignore errors
+ * @return true on success
+ */
+ bool AddHost(const char *hostname, unsigned port, GError **error_r);
+
+ /**
+ * Add a listener on a Unix domain socket.
+ *
+ * @param path the absolute socket path
+ * @param error_r location to store the error occurring, or NULL to
+ * ignore errors
+ * @return true on success
+ */
+ bool AddPath(const char *path, GError **error_r);
+
+ /**
+ * Add a socket descriptor that is accepting connections. After this
+ * has been called, don't call server_socket_open(), because the
+ * socket is already open.
+ */
+ bool AddFD(int fd, GError **error_r);
+
+ bool Open(GError **error_r);
+ void Close();
+
+protected:
+ virtual void OnAccept(int fd, const sockaddr &address,
+ size_t address_length, int uid) = 0;
+};
+
+#endif
diff --git a/src/event/SocketMonitor.cxx b/src/event/SocketMonitor.cxx
new file mode 100644
index 000000000..6efa69647
--- /dev/null
+++ b/src/event/SocketMonitor.cxx
@@ -0,0 +1,167 @@
+/*
+ * 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 "SocketMonitor.hxx"
+#include "Loop.hxx"
+#include "fd_util.h"
+#include "gcc.h"
+
+#include <assert.h>
+
+#ifdef WIN32
+#include <winsock2.h>
+#else
+#include <sys/types.h>
+#include <sys/socket.h>
+#endif
+
+/*
+ * 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);
+}
+
+SocketMonitor::~SocketMonitor()
+{
+ if (IsDefined())
+ Close();
+}
+
+void
+SocketMonitor::Open(int _fd)
+{
+ assert(fd < 0);
+ assert(source == nullptr);
+ assert(_fd >= 0);
+
+ fd = _fd;
+ 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);
+}
+
+int
+SocketMonitor::Steal()
+{
+ assert(IsDefined());
+
+ Cancel();
+
+ int result = fd;
+ fd = -1;
+
+ g_source_destroy(&source->base);
+ g_source_unref(&source->base);
+ source = nullptr;
+
+ return result;
+}
+
+void
+SocketMonitor::Close()
+{
+ close_socket(Steal());
+}
+
+SocketMonitor::ssize_t
+SocketMonitor::Read(void *data, size_t length)
+{
+ int flags = 0;
+#ifdef MSG_DONTWAIT
+ flags |= MSG_DONTWAIT;
+#endif
+
+ return recv(Get(), (char *)data, length, flags);
+}
+
+SocketMonitor::ssize_t
+SocketMonitor::Write(const void *data, size_t length)
+{
+ int flags = 0;
+#ifdef MSG_NOSIGNAL
+ flags |= MSG_NOSIGNAL;
+#endif
+#ifdef MSG_DONTWAIT
+ flags |= MSG_DONTWAIT;
+#endif
+
+ return send(Get(), (const char *)data, length, flags);
+}
+
+void
+SocketMonitor::CommitEventFlags()
+{
+ loop.WakeUp();
+}
diff --git a/src/event/SocketMonitor.hxx b/src/event/SocketMonitor.hxx
new file mode 100644
index 000000000..c60b8efdf
--- /dev/null
+++ b/src/event/SocketMonitor.hxx
@@ -0,0 +1,148 @@
+/*
+ * 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_SOCKET_MONITOR_HXX
+#define MPD_SOCKET_MONITOR_HXX
+
+#include "check.h"
+
+#include <glib.h>
+
+#include <type_traits>
+
+#include <assert.h>
+#include <stddef.h>
+
+#ifdef WIN32
+/* ERRORis a WIN32 macro that poisons our namespace; this is a
+ kludge to allow us to use it anyway */
+#ifdef ERROR
+#undef ERROR
+#endif
+#endif
+
+class EventLoop;
+
+class SocketMonitor {
+ struct Source {
+ GSource base;
+
+ SocketMonitor *monitor;
+ };
+
+ int fd;
+ EventLoop &loop;
+ Source *source;
+ GPollFD poll;
+
+public:
+ 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;
+
+ typedef std::make_signed<size_t>::type ssize_t;
+
+ SocketMonitor(EventLoop &_loop)
+ :fd(-1), loop(_loop), source(nullptr) {}
+
+ SocketMonitor(int _fd, EventLoop &_loop);
+
+ ~SocketMonitor();
+
+ bool IsDefined() const {
+ return fd >= 0;
+ }
+
+ int Get() const {
+ assert(IsDefined());
+
+ return fd;
+ }
+
+ void Open(int _fd);
+
+ /**
+ * "Steal" the socket descriptor. This abandons the socket
+ * and puts the responsibility for closing it to the caller.
+ */
+ int Steal();
+
+ void Close();
+
+ void Schedule(unsigned flags) {
+ poll.events = flags;
+ poll.revents &= flags;
+ CommitEventFlags();
+ }
+
+ void Cancel() {
+ poll.events = 0;
+ CommitEventFlags();
+ }
+
+ void ScheduleRead() {
+ poll.events |= READ|HANGUP|ERROR;
+ CommitEventFlags();
+ }
+
+ void ScheduleWrite() {
+ poll.events |= WRITE;
+ CommitEventFlags();
+ }
+
+ void CancelRead() {
+ poll.events &= ~(READ|HANGUP|ERROR);
+ CommitEventFlags();
+ }
+
+ void CancelWrite() {
+ poll.events &= ~WRITE;
+ CommitEventFlags();
+ }
+
+ ssize_t Read(void *data, size_t length);
+ ssize_t Write(const void *data, size_t length);
+
+protected:
+ /**
+ * @return false if the socket has been closed
+ */
+ virtual bool OnSocketReady(unsigned flags) = 0;
+
+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:
+ void CommitEventFlags();
+
+ bool Check() const {
+ return (poll.revents & poll.events) != 0;
+ }
+
+ void Dispatch() {
+ OnSocketReady(poll.revents & poll.events);
+ }
+};
+
+#endif
diff --git a/src/event/TimeoutMonitor.cxx b/src/event/TimeoutMonitor.cxx
new file mode 100644
index 000000000..8636292ac
--- /dev/null
+++ b/src/event/TimeoutMonitor.cxx
@@ -0,0 +1,61 @@
+/*
+ * 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 "TimeoutMonitor.hxx"
+#include "Loop.hxx"
+
+void
+TimeoutMonitor::Cancel()
+{
+ if (source != nullptr) {
+ g_source_destroy(source);
+ g_source_unref(source);
+ source = nullptr;
+ }
+}
+
+void
+TimeoutMonitor::Schedule(unsigned ms)
+{
+ Cancel();
+ source = loop.AddTimeout(ms, Callback, this);
+}
+
+void
+TimeoutMonitor::ScheduleSeconds(unsigned s)
+{
+ Cancel();
+ source = loop.AddTimeoutSeconds(s, Callback, this);
+}
+
+void
+TimeoutMonitor::Run()
+{
+ Cancel();
+ OnTimeout();
+}
+
+gboolean
+TimeoutMonitor::Callback(gpointer data)
+{
+ TimeoutMonitor &monitor = *(TimeoutMonitor *)data;
+ monitor.Run();
+ return false;
+}
diff --git a/src/event/TimeoutMonitor.hxx b/src/event/TimeoutMonitor.hxx
new file mode 100644
index 000000000..4ebc6b644
--- /dev/null
+++ b/src/event/TimeoutMonitor.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_SOCKET_TIMEOUT_MONITOR_HXX
+#define MPD_SOCKET_TIMEOUT_MONITOR_HXX
+
+#include "check.h"
+
+#include <glib.h>
+
+class EventLoop;
+
+class TimeoutMonitor {
+ EventLoop &loop;
+ GSource *source;
+
+public:
+ TimeoutMonitor(EventLoop &_loop)
+ :loop(_loop), source(nullptr) {}
+
+ ~TimeoutMonitor() {
+ Cancel();
+ }
+
+ bool IsActive() const {
+ return source != nullptr;
+ }
+
+ void Schedule(unsigned ms);
+ void ScheduleSeconds(unsigned s);
+ void Cancel();
+
+protected:
+ virtual void OnTimeout() = 0;
+
+private:
+ void Run();
+ static gboolean Callback(gpointer data);
+};
+
+#endif /* MAIN_NOTIFY_H */
diff --git a/src/event/WakeFD.cxx b/src/event/WakeFD.cxx
new file mode 100644
index 000000000..1a84f5645
--- /dev/null
+++ b/src/event/WakeFD.cxx
@@ -0,0 +1,225 @@
+/*
+ * 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 "WakeFD.hxx"
+#include "fd_util.h"
+#include "gcc.h"
+
+#include <unistd.h>
+
+#ifdef WIN32
+#include <ws2tcpip.h>
+#include <winsock2.h>
+#include <cstring> /* for memset() */
+#endif
+
+#ifdef HAVE_EVENTFD
+#include <sys/eventfd.h>
+#endif
+
+#ifdef WIN32
+static bool PoorSocketPair(int fd[2]);
+#endif
+
+bool
+WakeFD::Create()
+{
+ assert(fds[0] == -1);
+ assert(fds[1] == -1);
+
+#ifdef WIN32
+ return PoorSocketPair(fds);
+#else
+#ifdef HAVE_EVENTFD
+ fds[0] = eventfd_cloexec_nonblock(0, 0);
+ if (fds[0] >= 0) {
+ fds[1] = -2;
+ return true;
+ }
+#endif
+ return pipe_cloexec_nonblock(fds) >= 0;
+#endif
+}
+
+void
+WakeFD::Destroy()
+{
+#ifdef WIN32
+ closesocket(fds[0]);
+ closesocket(fds[1]);
+#else
+ close(fds[0]);
+#ifdef HAVE_EVENTFD
+ if (!IsEventFD())
+#endif
+ close(fds[1]);
+#endif
+
+#ifndef NDEBUG
+ fds[0] = -1;
+ fds[1] = -1;
+#endif
+}
+
+bool
+WakeFD::Read()
+{
+ assert(fds[0] >= 0);
+
+#ifdef WIN32
+ assert(fds[1] >= 0);
+ char buffer[256];
+ return recv(fds[0], buffer, sizeof(buffer), 0) > 0;
+#else
+
+#ifdef HAVE_EVENTFD
+ if (IsEventFD()) {
+ eventfd_t value;
+ return read(fds[0], &value,
+ sizeof(value)) == (ssize_t)sizeof(value);
+ }
+#endif
+
+ assert(fds[1] >= 0);
+
+ char buffer[256];
+ return read(fds[0], buffer, sizeof(buffer)) > 0;
+#endif
+}
+
+void
+WakeFD::Write()
+{
+ assert(fds[0] >= 0);
+
+#ifdef WIN32
+ assert(fds[1] >= 0);
+
+ send(fds[1], "", 1, 0);
+#else
+
+#ifdef HAVE_EVENTFD
+ if (IsEventFD()) {
+ static constexpr eventfd_t value = 1;
+ gcc_unused ssize_t nbytes =
+ write(fds[0], &value, sizeof(value));
+ return;
+ }
+#endif
+
+ assert(fds[1] >= 0);
+
+ gcc_unused ssize_t nbytes = write(fds[1], "", 1);
+#endif
+}
+
+#ifdef WIN32
+
+static void SafeCloseSocket(SOCKET s)
+{
+ int error = WSAGetLastError();
+ closesocket(s);
+ WSASetLastError(error);
+}
+
+/* Our poor man's socketpair() implementation
+ * Due to limited protocol/address family support and primitive error handling
+ * it's better to keep this as a private implementation detail of WakeFD
+ * rather than wide-available API.
+ */
+static bool PoorSocketPair(int fd[2])
+{
+ assert (fd != nullptr);
+
+ SOCKET listen_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (listen_socket == INVALID_SOCKET)
+ return false;
+
+ sockaddr_in address;
+ std::memset(&address, 0, sizeof(address));
+ address.sin_family = AF_INET;
+ address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+ int ret = bind(listen_socket,
+ reinterpret_cast<sockaddr*>(&address),
+ sizeof(address));
+
+ if (ret < 0) {
+ SafeCloseSocket(listen_socket);
+ return false;
+ }
+
+ ret = listen(listen_socket, 1);
+
+ if (ret < 0) {
+ SafeCloseSocket(listen_socket);
+ return false;
+ }
+
+ int address_len = sizeof(address);
+ ret = getsockname(listen_socket,
+ reinterpret_cast<sockaddr*>(&address),
+ &address_len);
+
+ if (ret < 0) {
+ SafeCloseSocket(listen_socket);
+ return false;
+ }
+
+ SOCKET socket0 = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (socket0 == INVALID_SOCKET) {
+ SafeCloseSocket(listen_socket);
+ return false;
+ }
+
+ ret = connect(socket0,
+ reinterpret_cast<sockaddr*>(&address),
+ sizeof(address));
+
+ if (ret < 0) {
+ SafeCloseSocket(listen_socket);
+ SafeCloseSocket(socket0);
+ return false;
+ }
+
+ SOCKET socket1 = accept(listen_socket, nullptr, nullptr);
+ if (socket1 == INVALID_SOCKET) {
+ SafeCloseSocket(listen_socket);
+ SafeCloseSocket(socket0);
+ return false;
+ }
+
+ SafeCloseSocket(listen_socket);
+
+ u_long non_block = 1;
+ if (ioctlsocket(socket0, FIONBIO, &non_block) < 0
+ || ioctlsocket(socket1, FIONBIO, &non_block) < 0) {
+ SafeCloseSocket(socket0);
+ SafeCloseSocket(socket1);
+ return false;
+ }
+
+ fd[0] = static_cast<int>(socket0);
+ fd[1] = static_cast<int>(socket1);
+
+ return true;
+}
+
+#endif
diff --git a/src/event/WakeFD.hxx b/src/event/WakeFD.hxx
new file mode 100644
index 000000000..15b66b4cf
--- /dev/null
+++ b/src/event/WakeFD.hxx
@@ -0,0 +1,80 @@
+/*
+ * 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_WAKE_FD_HXX
+#define MPD_WAKE_FD_HXX
+
+#include "check.h"
+
+#include <assert.h>
+
+/**
+ * This class can be used to wake up an I/O event loop.
+ *
+ * For optimization purposes, this class does not have a constructor
+ * or a destructor.
+ */
+class WakeFD {
+ int fds[2];
+
+public:
+#ifdef NDEBUG
+ WakeFD() = default;
+#else
+ WakeFD():fds{-1, -1} {};
+#endif
+
+ WakeFD(const WakeFD &other) = delete;
+ WakeFD &operator=(const WakeFD &other) = delete;
+
+ bool Create();
+ void Destroy();
+
+ int Get() const {
+ assert(fds[0] >= 0);
+#ifndef HAVE_EVENTFD
+ assert(fds[1] >= 0);
+#endif
+
+ return fds[0];
+ }
+
+ /**
+ * Checks if Write() was called at least once since the last
+ * Read() call.
+ */
+ bool Read();
+
+ /**
+ * Wakes up the reader. Multiple calls to this function will
+ * be combined to one wakeup.
+ */
+ void Write();
+
+private:
+#ifdef HAVE_EVENTFD
+ bool IsEventFD() {
+ assert(fds[0] >= 0);
+
+ return fds[1] == -2;
+ }
+#endif
+};
+
+#endif /* MAIN_NOTIFY_H */
diff --git a/src/event_pipe.c b/src/event_pipe.c
deleted file mode 100644
index d5c3b9564..000000000
--- a/src/event_pipe.c
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "event_pipe.h"
-#include "fd_util.h"
-#include "mpd_error.h"
-
-#include <stdbool.h>
-#include <assert.h>
-#include <glib.h>
-#include <string.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#ifdef WIN32
-/* for _O_BINARY */
-#include <fcntl.h>
-#endif
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "event_pipe"
-
-static int event_pipe[2];
-static GIOChannel *event_channel;
-static guint event_pipe_source_id;
-static GMutex *event_pipe_mutex;
-static bool pipe_events[PIPE_EVENT_MAX];
-static event_pipe_callback_t event_pipe_callbacks[PIPE_EVENT_MAX];
-
-/**
- * Invoke the callback for a certain event.
- */
-static void
-event_pipe_invoke(enum pipe_event event)
-{
- assert((unsigned)event < PIPE_EVENT_MAX);
- assert(event_pipe_callbacks[event] != NULL);
-
- event_pipe_callbacks[event]();
-}
-
-static gboolean
-main_notify_event(G_GNUC_UNUSED GIOChannel *source,
- G_GNUC_UNUSED GIOCondition condition,
- G_GNUC_UNUSED gpointer data)
-{
- char buffer[256];
- gsize bytes_read;
- GError *error = NULL;
- GIOStatus status = g_io_channel_read_chars(event_channel,
- buffer, sizeof(buffer),
- &bytes_read, &error);
- if (status == G_IO_STATUS_ERROR)
- MPD_ERROR("error reading from pipe: %s", error->message);
-
- bool events[PIPE_EVENT_MAX];
- g_mutex_lock(event_pipe_mutex);
- memcpy(events, pipe_events, sizeof(events));
- memset(pipe_events, 0, sizeof(pipe_events));
- g_mutex_unlock(event_pipe_mutex);
-
- for (unsigned i = 0; i < PIPE_EVENT_MAX; ++i)
- if (events[i])
- /* invoke the event handler */
- event_pipe_invoke(i);
-
- return true;
-}
-
-void event_pipe_init(void)
-{
- GIOChannel *channel;
- int ret;
-
- ret = pipe_cloexec_nonblock(event_pipe);
- if (ret < 0)
- MPD_ERROR("Couldn't open pipe: %s", strerror(errno));
-
-#ifndef G_OS_WIN32
- channel = g_io_channel_unix_new(event_pipe[0]);
-#else
- channel = g_io_channel_win32_new_fd(event_pipe[0]);
-#endif
- g_io_channel_set_encoding(channel, NULL, NULL);
- g_io_channel_set_buffered(channel, false);
-
- event_pipe_source_id = g_io_add_watch(channel, G_IO_IN,
- main_notify_event, NULL);
-
- event_channel = channel;
-
- event_pipe_mutex = g_mutex_new();
-}
-
-void event_pipe_deinit(void)
-{
- g_mutex_free(event_pipe_mutex);
-
- g_source_remove(event_pipe_source_id);
- g_io_channel_unref(event_channel);
-
-#ifndef WIN32
- /* By some strange reason this call hangs on Win32 */
- close(event_pipe[0]);
-#endif
- close(event_pipe[1]);
-}
-
-void
-event_pipe_register(enum pipe_event event, event_pipe_callback_t callback)
-{
- assert((unsigned)event < PIPE_EVENT_MAX);
- assert(event_pipe_callbacks[event] == NULL);
-
- event_pipe_callbacks[event] = callback;
-}
-
-void event_pipe_emit(enum pipe_event event)
-{
- ssize_t w;
-
- assert((unsigned)event < PIPE_EVENT_MAX);
-
- g_mutex_lock(event_pipe_mutex);
- if (pipe_events[event]) {
- /* already set: don't write */
- g_mutex_unlock(event_pipe_mutex);
- return;
- }
-
- pipe_events[event] = true;
- g_mutex_unlock(event_pipe_mutex);
-
- w = write(event_pipe[1], "", 1);
- if (w < 0 && errno != EAGAIN && errno != EINTR)
- MPD_ERROR("error writing to pipe: %s", strerror(errno));
-}
-
-void event_pipe_emit_fast(enum pipe_event event)
-{
- assert((unsigned)event < PIPE_EVENT_MAX);
-
- pipe_events[event] = true;
-
- G_GNUC_UNUSED ssize_t nbytes = write(event_pipe[1], "", 1);
-}
diff --git a/src/event_pipe.h b/src/event_pipe.h
deleted file mode 100644
index 3734bb86c..000000000
--- a/src/event_pipe.h
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * 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 EVENT_PIPE_H
-#define EVENT_PIPE_H
-
-#include <glib.h>
-
-enum pipe_event {
- /** database update was finished */
- PIPE_EVENT_UPDATE,
-
- /** during database update, a song was deleted */
- PIPE_EVENT_DELETE,
-
- /** an idle event was emitted */
- PIPE_EVENT_IDLE,
-
- /** must call playlist_sync() */
- PIPE_EVENT_PLAYLIST,
-
- /** the current song's tag has changed */
- PIPE_EVENT_TAG,
-
- /** SIGHUP received: reload configuration, roll log file */
- PIPE_EVENT_RELOAD,
-
- /** a hardware mixer plugin has detected a change */
- PIPE_EVENT_MIXER,
-
- /** shutdown requested */
- PIPE_EVENT_SHUTDOWN,
-
- PIPE_EVENT_MAX
-};
-
-typedef void (*event_pipe_callback_t)(void);
-
-void event_pipe_init(void);
-
-void event_pipe_deinit(void);
-
-void
-event_pipe_register(enum pipe_event event, event_pipe_callback_t callback);
-
-void event_pipe_emit(enum pipe_event event);
-
-/**
- * Similar to event_pipe_emit(), but aimed for use in signal handlers:
- * it doesn't lock the mutex, and doesn't log on error. That makes it
- * potentially lossy, but for its intended use, that does not matter.
- */
-void event_pipe_emit_fast(enum pipe_event event);
-
-#endif /* MAIN_NOTIFY_H */
diff --git a/src/exclude.c b/src/exclude.c
deleted file mode 100644
index 438039d30..000000000
--- a/src/exclude.c
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * The .mpdignore backend code.
- *
- */
-
-#include "config.h"
-#include "exclude.h"
-#include "path.h"
-
-#include <assert.h>
-#include <string.h>
-#include <stdio.h>
-#include <errno.h>
-
-GSList *
-exclude_list_load(const char *path_fs)
-{
- FILE *file;
- char line[1024];
- GSList *list = NULL;
-
- assert(path_fs != NULL);
-
- file = fopen(path_fs, "r");
- if (file == NULL) {
- if (errno != ENOENT) {
- char *path_utf8 = fs_charset_to_utf8(path_fs);
- g_debug("Failed to open %s: %s",
- path_utf8, g_strerror(errno));
- g_free(path_utf8);
- }
-
- return NULL;
- }
-
- while (fgets(line, sizeof(line), file) != NULL) {
- char *p = strchr(line, '#');
- if (p != NULL)
- *p = 0;
-
- p = g_strstrip(line);
- if (*p != 0)
- list = g_slist_prepend(list, g_pattern_spec_new(p));
- }
-
- fclose(file);
-
- return list;
-}
-
-void
-exclude_list_free(GSList *list)
-{
- while (list != NULL) {
- GPatternSpec *pattern = list->data;
- g_pattern_spec_free(pattern);
- list = g_slist_remove(list, list->data);
- }
-}
-
-bool
-exclude_list_check(GSList *list, const char *name_fs)
-{
- assert(name_fs != NULL);
-
- /* XXX include full path name in check */
-
- for (; list != NULL; list = list->next) {
- GPatternSpec *pattern = list->data;
-
- if (g_pattern_match_string(pattern, name_fs))
- return true;
- }
-
- return false;
-}
diff --git a/src/exclude.h b/src/exclude.h
deleted file mode 100644
index 5b1229e29..000000000
--- a/src/exclude.h
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * The .mpdignore backend code.
- *
- */
-
-#ifndef MPD_EXCLUDE_H
-#define MPD_EXCLUDE_H
-
-#include <glib.h>
-
-#include <stdbool.h>
-
-/**
- * Loads and parses a .mpdignore file.
- */
-GSList *
-exclude_list_load(const char *path_fs);
-
-/**
- * Frees a list returned by exclude_list_load().
- */
-void
-exclude_list_free(GSList *list);
-
-/**
- * Checks whether one of the patterns in the .mpdignore file matches
- * the specified file name.
- */
-bool
-exclude_list_check(GSList *list, const char *name_fs);
-
-#endif
diff --git a/src/fd_util.c b/src/fd_util.c
index 882b4c7d5..ea29d6eaa 100644
--- a/src/fd_util.c
+++ b/src/fd_util.c
@@ -49,6 +49,10 @@
#include <sys/inotify.h>
#endif
+#ifdef HAVE_EVENTFD
+#include <sys/eventfd.h>
+#endif
+
#ifndef WIN32
static int
@@ -328,6 +332,16 @@ inotify_init_cloexec(void)
#endif
+#ifdef HAVE_EVENTFD
+
+int
+eventfd_cloexec_nonblock(unsigned initval, int flags)
+{
+ return eventfd(initval, flags | EFD_CLOEXEC | EFD_NONBLOCK);
+}
+
+#endif
+
int
close_socket(int fd)
{
diff --git a/src/fd_util.h b/src/fd_util.h
index dd4df7a13..e65c6a69b 100644
--- a/src/fd_util.h
+++ b/src/fd_util.h
@@ -51,6 +51,10 @@
struct sockaddr;
+#ifdef __cplusplus
+extern "C" {
+#endif
+
/**
* Wrapper for dup(), which sets the CLOEXEC flag on the new
* descriptor.
@@ -140,10 +144,25 @@ inotify_init_cloexec(void);
#endif
+#ifdef HAVE_EVENTFD
+
+/**
+ * Wrapper for eventfd() which sets the flags CLOEXEC and NONBLOCK
+ * flag (atomically if supported by the OS).
+ */
+int
+eventfd_cloexec_nonblock(unsigned initval, int flags);
+
+#endif
+
/**
* Portable wrapper for close(); use closesocket() on WIN32/WinSock.
*/
int
close_socket(int fd);
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
#endif
diff --git a/src/fifo_buffer.c b/src/fifo_buffer.c
deleted file mode 100644
index 915fb0579..000000000
--- a/src/fifo_buffer.c
+++ /dev/null
@@ -1,210 +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;
-}
-
-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/fifo_buffer.h b/src/fifo_buffer.h
deleted file mode 100644
index 3bdb23938..000000000
--- a/src/fifo_buffer.h
+++ /dev/null
@@ -1,153 +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;
-
-/**
- * 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);
-
-/**
- * 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);
-
-#endif
diff --git a/src/filter/AutoConvertFilterPlugin.cxx b/src/filter/AutoConvertFilterPlugin.cxx
new file mode 100644
index 000000000..19495acbc
--- /dev/null
+++ b/src/filter/AutoConvertFilterPlugin.cxx
@@ -0,0 +1,126 @@
+/*
+ * 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 "AutoConvertFilterPlugin.hxx"
+#include "ConvertFilterPlugin.hxx"
+#include "FilterPlugin.hxx"
+#include "FilterInternal.hxx"
+#include "FilterRegistry.hxx"
+#include "AudioFormat.hxx"
+#include "ConfigData.hxx"
+
+#include <assert.h>
+
+class AutoConvertFilter final : public Filter {
+ /**
+ * The underlying filter.
+ */
+ Filter *filter;
+
+ /**
+ * A convert_filter, just in case conversion is needed. nullptr
+ * if unused.
+ */
+ Filter *convert;
+
+public:
+ AutoConvertFilter(Filter *_filter):filter(_filter) {}
+ ~AutoConvertFilter() {
+ delete filter;
+ }
+
+ virtual AudioFormat Open(AudioFormat &af, GError **error_r);
+ virtual void Close();
+ virtual const void *FilterPCM(const void *src, size_t src_size,
+ size_t *dest_size_r, GError **error_r);
+};
+
+AudioFormat
+AutoConvertFilter::Open(AudioFormat &in_audio_format, GError **error_r)
+{
+ assert(in_audio_format.IsValid());
+
+ /* open the "real" filter */
+
+ const AudioFormat child_audio_format = in_audio_format;
+ AudioFormat out_audio_format = filter->Open(in_audio_format, error_r);
+ if (!out_audio_format.IsDefined())
+ return out_audio_format;
+
+ /* need to convert? */
+
+ if (in_audio_format != child_audio_format) {
+ /* yes - create a convert_filter */
+
+ const config_param empty;
+ convert = filter_new(&convert_filter_plugin, empty, error_r);
+ if (convert == nullptr) {
+ filter->Close();
+ return AudioFormat::Undefined();
+ }
+
+ AudioFormat audio_format2 = in_audio_format;
+ AudioFormat audio_format3 =
+ convert->Open(audio_format2, error_r);
+ if (!audio_format3.IsDefined()) {
+ delete convert;
+ filter->Close();
+ return AudioFormat::Undefined();
+ }
+
+ assert(audio_format2 == in_audio_format);
+
+ convert_filter_set(convert, child_audio_format);
+ } else
+ /* no */
+ convert = nullptr;
+
+ return out_audio_format;
+}
+
+void
+AutoConvertFilter::Close()
+{
+ if (convert != nullptr) {
+ convert->Close();
+ delete convert;
+ }
+
+ filter->Close();
+}
+
+const void *
+AutoConvertFilter::FilterPCM(const void *src, size_t src_size,
+ size_t *dest_size_r, GError **error_r)
+{
+ if (convert != nullptr) {
+ src = convert->FilterPCM(src, src_size, &src_size, error_r);
+ if (src == nullptr)
+ return nullptr;
+ }
+
+ return filter->FilterPCM(src, src_size, dest_size_r, error_r);
+}
+
+Filter *
+autoconvert_filter_new(Filter *filter)
+{
+ return new AutoConvertFilter(filter);
+}
diff --git a/src/filter/AutoConvertFilterPlugin.hxx b/src/filter/AutoConvertFilterPlugin.hxx
new file mode 100644
index 000000000..7db72a345
--- /dev/null
+++ b/src/filter/AutoConvertFilterPlugin.hxx
@@ -0,0 +1,34 @@
+/*
+ * 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_AUTOCONVERT_FILTER_PLUGIN_HXX
+#define MPD_AUTOCONVERT_FILTER_PLUGIN_HXX
+
+class Filter;
+
+/**
+ * Creates a new "autoconvert" filter. When opened, it ensures that
+ * the input audio format isn't changed. If the underlying filter
+ * requests a different format, it automatically creates a
+ * convert_filter.
+ */
+Filter *
+autoconvert_filter_new(Filter *filter);
+
+#endif
diff --git a/src/filter/ChainFilterPlugin.cxx b/src/filter/ChainFilterPlugin.cxx
new file mode 100644
index 000000000..fbb91795d
--- /dev/null
+++ b/src/filter/ChainFilterPlugin.cxx
@@ -0,0 +1,185 @@
+/*
+ * 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 "ChainFilterPlugin.hxx"
+#include "conf.h"
+#include "FilterPlugin.hxx"
+#include "FilterInternal.hxx"
+#include "FilterRegistry.hxx"
+#include "AudioFormat.hxx"
+
+#include <glib.h>
+
+#include <list>
+
+#include <assert.h>
+
+class ChainFilter final : public Filter {
+ struct Child {
+ const char *name;
+ Filter *filter;
+
+ Child(const char *_name, Filter *_filter)
+ :name(_name), filter(_filter) {}
+ ~Child() {
+ delete filter;
+ }
+
+ Child(const Child &) = delete;
+ Child &operator=(const Child &) = delete;
+ };
+
+ std::list<Child> children;
+
+public:
+ void Append(const char *name, Filter *filter) {
+ children.emplace_back(name, filter);
+ }
+
+ virtual AudioFormat Open(AudioFormat &af, GError **error_r) override;
+ virtual void Close();
+ virtual const void *FilterPCM(const void *src, size_t src_size,
+ size_t *dest_size_r, GError **error_r);
+
+private:
+ /**
+ * Close all filters in the chain until #until is reached.
+ * #until itself is not closed.
+ */
+ void CloseUntil(const Filter *until);
+};
+
+static inline GQuark
+filter_quark(void)
+{
+ return g_quark_from_static_string("filter");
+}
+
+static Filter *
+chain_filter_init(gcc_unused const config_param &param,
+ gcc_unused GError **error_r)
+{
+ return new ChainFilter();
+}
+
+void
+ChainFilter::CloseUntil(const Filter *until)
+{
+ for (auto &child : children) {
+ if (child.filter == until)
+ /* don't close this filter */
+ return;
+
+ /* close this filter */
+ child.filter->Close();
+ }
+
+ /* this assertion fails if #until does not exist (anymore) */
+ assert(false);
+ gcc_unreachable();
+}
+
+static AudioFormat
+chain_open_child(const char *name, Filter *filter,
+ const AudioFormat &prev_audio_format,
+ GError **error_r)
+{
+ AudioFormat conv_audio_format = prev_audio_format;
+ const AudioFormat next_audio_format =
+ filter->Open(conv_audio_format, error_r);
+ if (!next_audio_format.IsDefined())
+ return next_audio_format;
+
+ if (conv_audio_format != prev_audio_format) {
+ struct audio_format_string s;
+
+ filter->Close();
+ g_set_error(error_r, filter_quark(), 0,
+ "Audio format not supported by filter '%s': %s",
+ name,
+ audio_format_to_string(prev_audio_format, &s));
+ return AudioFormat::Undefined();
+ }
+
+ return next_audio_format;
+}
+
+AudioFormat
+ChainFilter::Open(AudioFormat &in_audio_format, GError **error_r)
+{
+ AudioFormat audio_format = in_audio_format;
+
+ for (auto &child : children) {
+ audio_format = chain_open_child(child.name, child.filter,
+ audio_format, error_r);
+ if (!audio_format.IsDefined()) {
+ /* rollback, close all children */
+ CloseUntil(child.filter);
+ break;
+ }
+ }
+
+ /* return the output format of the last filter */
+ return audio_format;
+}
+
+void
+ChainFilter::Close()
+{
+ for (auto &child : children)
+ child.filter->Close();
+}
+
+const void *
+ChainFilter::FilterPCM(const void *src, size_t src_size,
+ size_t *dest_size_r, GError **error_r)
+{
+ for (auto &child : children) {
+ /* feed the output of the previous filter as input
+ into the current one */
+ src = child.filter->FilterPCM(src, src_size, &src_size,
+ error_r);
+ if (src == NULL)
+ return NULL;
+ }
+
+ /* return the output of the last filter */
+ *dest_size_r = src_size;
+ return src;
+}
+
+const struct filter_plugin chain_filter_plugin = {
+ "chain",
+ chain_filter_init,
+};
+
+Filter *
+filter_chain_new(void)
+{
+ return new ChainFilter();
+}
+
+void
+filter_chain_append(Filter &_chain, const char *name, Filter *filter)
+{
+ ChainFilter &chain = (ChainFilter &)_chain;
+
+ chain.Append(name, filter);
+}
diff --git a/src/filter/ChainFilterPlugin.hxx b/src/filter/ChainFilterPlugin.hxx
new file mode 100644
index 000000000..884c7ca19
--- /dev/null
+++ b/src/filter/ChainFilterPlugin.hxx
@@ -0,0 +1,48 @@
+/*
+ * 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
+ *
+ * A filter chain is a container for several filters. They are
+ * chained together, i.e. called in a row, one filter passing its
+ * output to the next one.
+ */
+
+#ifndef MPD_FILTER_CHAIN_HXX
+#define MPD_FILTER_CHAIN_HXX
+
+class Filter;
+
+/**
+ * Creates a new filter chain.
+ */
+Filter *
+filter_chain_new(void);
+
+/**
+ * Appends a new filter at the end of the filter chain. You must call
+ * this function before the first filter_open() call.
+ *
+ * @param chain the filter chain created with filter_chain_new()
+ * @param filter the filter to be appended to #chain
+ */
+void
+filter_chain_append(Filter &chain, const char *name, Filter *filter);
+
+#endif
diff --git a/src/filter/ConvertFilterPlugin.cxx b/src/filter/ConvertFilterPlugin.cxx
new file mode 100644
index 000000000..4dc0d0333
--- /dev/null
+++ b/src/filter/ConvertFilterPlugin.cxx
@@ -0,0 +1,119 @@
+/*
+ * 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 "ConvertFilterPlugin.hxx"
+#include "FilterPlugin.hxx"
+#include "FilterInternal.hxx"
+#include "FilterRegistry.hxx"
+#include "conf.h"
+#include "pcm/PcmConvert.hxx"
+#include "util/Manual.hxx"
+#include "AudioFormat.hxx"
+#include "poison.h"
+
+#include <assert.h>
+#include <string.h>
+
+class ConvertFilter final : public Filter {
+ /**
+ * The input audio format; PCM data is passed to the filter()
+ * method in this format.
+ */
+ AudioFormat in_audio_format;
+
+ /**
+ * 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().
+ */
+ 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;
+ }
+
+ virtual AudioFormat Open(AudioFormat &af, GError **error_r) override;
+ virtual void Close();
+ virtual const void *FilterPCM(const void *src, size_t src_size,
+ size_t *dest_size_r, GError **error_r);
+};
+
+static Filter *
+convert_filter_init(gcc_unused const config_param &param,
+ gcc_unused GError **error_r)
+{
+ return new ConvertFilter();
+}
+
+AudioFormat
+ConvertFilter::Open(AudioFormat &audio_format, gcc_unused GError **error_r)
+{
+ assert(audio_format.IsValid());
+
+ in_audio_format = out_audio_format = audio_format;
+ state.Construct();
+
+ return in_audio_format;
+}
+
+void
+ConvertFilter::Close()
+{
+ state.Destruct();
+
+ poison_undefined(&in_audio_format, sizeof(in_audio_format));
+ poison_undefined(&out_audio_format, sizeof(out_audio_format));
+}
+
+const void *
+ConvertFilter::FilterPCM(const void *src, size_t src_size,
+ size_t *dest_size_r, GError **error_r)
+{
+ if (in_audio_format == out_audio_format) {
+ /* 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,
+ error_r);
+}
+
+const struct filter_plugin convert_filter_plugin = {
+ "convert",
+ convert_filter_init,
+};
+
+void
+convert_filter_set(Filter *_filter, const AudioFormat out_audio_format)
+{
+ ConvertFilter *filter = (ConvertFilter *)_filter;
+
+ filter->Set(out_audio_format);
+}
diff --git a/src/filter/ConvertFilterPlugin.hxx b/src/filter/ConvertFilterPlugin.hxx
new file mode 100644
index 000000000..c814aaf49
--- /dev/null
+++ b/src/filter/ConvertFilterPlugin.hxx
@@ -0,0 +1,35 @@
+/*
+ * 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_CONVERT_FILTER_PLUGIN_HXX
+#define MPD_CONVERT_FILTER_PLUGIN_HXX
+
+class Filter;
+struct AudioFormat;
+
+/**
+ * Sets the output audio format for the specified filter. You must
+ * call this after the filter has been opened. Since this audio
+ * 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);
+
+#endif
diff --git a/src/filter/NormalizeFilterPlugin.cxx b/src/filter/NormalizeFilterPlugin.cxx
new file mode 100644
index 000000000..63b562fa0
--- /dev/null
+++ b/src/filter/NormalizeFilterPlugin.cxx
@@ -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.
+ */
+
+#include "config.h"
+#include "FilterPlugin.hxx"
+#include "FilterInternal.hxx"
+#include "FilterRegistry.hxx"
+#include "pcm/PcmBuffer.hxx"
+#include "AudioFormat.hxx"
+#include "AudioCompress/compress.h"
+
+#include <assert.h>
+#include <string.h>
+
+class NormalizeFilter final : public Filter {
+ struct Compressor *compressor;
+
+ PcmBuffer buffer;
+
+public:
+ virtual AudioFormat Open(AudioFormat &af, GError **error_r) override;
+ virtual void Close();
+ virtual const void *FilterPCM(const void *src, size_t src_size,
+ size_t *dest_size_r, GError **error_r);
+};
+
+static Filter *
+normalize_filter_init(gcc_unused const config_param &param,
+ gcc_unused GError **error_r)
+{
+ return new NormalizeFilter();
+}
+
+AudioFormat
+NormalizeFilter::Open(AudioFormat &audio_format, gcc_unused GError **error_r)
+{
+ audio_format.format = SampleFormat::S16;
+
+ compressor = Compressor_new(0);
+
+ return audio_format;
+}
+
+void
+NormalizeFilter::Close()
+{
+ buffer.Clear();
+ Compressor_delete(compressor);
+}
+
+const void *
+NormalizeFilter::FilterPCM(const void *src, size_t src_size,
+ size_t *dest_size_r, gcc_unused GError **error_r)
+{
+ int16_t *dest = (int16_t *)buffer.Get(src_size);
+ memcpy(dest, src, src_size);
+
+ Compressor_Process_int16(compressor, dest, src_size / 2);
+
+ *dest_size_r = src_size;
+ return dest;
+}
+
+const struct filter_plugin normalize_filter_plugin = {
+ "normalize",
+ normalize_filter_init,
+};
diff --git a/src/filter/NullFilterPlugin.cxx b/src/filter/NullFilterPlugin.cxx
new file mode 100644
index 000000000..f76c05d3f
--- /dev/null
+++ b/src/filter/NullFilterPlugin.cxx
@@ -0,0 +1,61 @@
+/*
+ * 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
+ *
+ * This filter plugin does nothing. That is not quite useful, except
+ * for testing the filter core, or as a template for new filter
+ * plugins.
+ */
+
+#include "config.h"
+#include "FilterPlugin.hxx"
+#include "FilterInternal.hxx"
+#include "FilterRegistry.hxx"
+#include "AudioFormat.hxx"
+#include "gcc.h"
+
+class NullFilter final : public Filter {
+public:
+ virtual AudioFormat Open(AudioFormat &af,
+ gcc_unused GError **error_r) {
+ return af;
+ }
+
+ virtual void Close() {}
+
+ virtual const void *FilterPCM(const void *src, size_t src_size,
+ size_t *dest_size_r,
+ gcc_unused GError **error_r) {
+ *dest_size_r = src_size;
+ return src;
+ }
+};
+
+static Filter *
+null_filter_init(gcc_unused const config_param &param,
+ gcc_unused GError **error_r)
+{
+ return new NullFilter();
+}
+
+const struct filter_plugin null_filter_plugin = {
+ "null",
+ null_filter_init,
+};
diff --git a/src/filter/ReplayGainFilterPlugin.cxx b/src/filter/ReplayGainFilterPlugin.cxx
new file mode 100644
index 000000000..5548483da
--- /dev/null
+++ b/src/filter/ReplayGainFilterPlugin.cxx
@@ -0,0 +1,238 @@
+/*
+ * 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 "ReplayGainFilterPlugin.hxx"
+#include "FilterPlugin.hxx"
+#include "FilterInternal.hxx"
+#include "FilterRegistry.hxx"
+#include "AudioFormat.hxx"
+#include "replay_gain_info.h"
+#include "replay_gain_config.h"
+#include "MixerControl.hxx"
+#include "pcm/PcmVolume.hxx"
+#include "pcm/PcmBuffer.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "replay_gain"
+
+class ReplayGainFilter final : public Filter {
+ /**
+ * If set, then this hardware mixer is used for applying
+ * replay gain, instead of the software volume library.
+ */
+ Mixer *mixer;
+
+ /**
+ * The base volume level for scale=1.0, between 1 and 100
+ * (including).
+ */
+ unsigned base;
+
+ enum replay_gain_mode mode;
+
+ struct replay_gain_info info;
+
+ /**
+ * The current volume, 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.
+ *
+ * On the other hand, if the user has set replaygain_limit to false,
+ * the chance of clipping is explicitly preferred if that's required to
+ * 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;
+
+public:
+ ReplayGainFilter()
+ :mixer(nullptr), mode(REPLAY_GAIN_OFF),
+ volume(PCM_VOLUME_1) {
+ replay_gain_info_init(&info);
+ }
+
+ void SetMixer(Mixer *_mixer, unsigned _base) {
+ assert(_mixer == NULL || (_base > 0 && _base <= 100));
+
+ mixer = _mixer;
+ base = _base;
+
+ Update();
+ }
+
+ void SetInfo(const struct replay_gain_info *_info) {
+ if (_info != NULL) {
+ info = *_info;
+ replay_gain_info_complete(&info);
+ } else
+ replay_gain_info_init(&info);
+
+ Update();
+ }
+
+ void SetMode(enum replay_gain_mode _mode) {
+ if (_mode == mode)
+ /* no change */
+ return;
+
+ g_debug("replay gain mode has changed %d->%d\n", mode, _mode);
+
+ mode = _mode;
+ Update();
+ }
+
+ /**
+ * Recalculates the new volume after a property was changed.
+ */
+ void Update();
+
+ virtual AudioFormat Open(AudioFormat &af, GError **error_r) override;
+ virtual void Close();
+ virtual const void *FilterPCM(const void *src, size_t src_size,
+ size_t *dest_size_r, GError **error_r);
+};
+
+static inline GQuark
+replay_gain_quark(void)
+{
+ return g_quark_from_static_string("replay_gain");
+}
+
+void
+ReplayGainFilter::Update()
+{
+ if (mode != REPLAY_GAIN_OFF) {
+ float scale = replay_gain_tuple_scale(&info.tuples[mode],
+ replay_gain_preamp, replay_gain_missing_preamp, replay_gain_limit);
+ g_debug("scale=%f\n", (double)scale);
+
+ volume = pcm_float_to_volume(scale);
+ } else
+ volume = PCM_VOLUME_1;
+
+ if (mixer != NULL) {
+ /* update the hardware mixer volume */
+
+ unsigned _volume = (volume * base) / PCM_VOLUME_1;
+ if (_volume > 100)
+ _volume = 100;
+
+ GError *error = NULL;
+ if (!mixer_set_volume(mixer, _volume, &error)) {
+ g_warning("Failed to update hardware mixer: %s",
+ error->message);
+ g_error_free(error);
+ }
+ }
+}
+
+static Filter *
+replay_gain_filter_init(gcc_unused const config_param &param,
+ gcc_unused GError **error_r)
+{
+ return new ReplayGainFilter();
+}
+
+AudioFormat
+ReplayGainFilter::Open(AudioFormat &af, gcc_unused GError **error_r)
+{
+ format = af;
+
+ return format;
+}
+
+void
+ReplayGainFilter::Close()
+{
+ buffer.Clear();
+}
+
+const void *
+ReplayGainFilter::FilterPCM(const void *src, size_t src_size,
+ size_t *dest_size_r, GError **error_r)
+{
+
+ *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) {
+ g_set_error(error_r, replay_gain_quark(), 0,
+ "pcm_volume() has failed");
+ return NULL;
+ }
+
+ return dest;
+}
+
+const struct filter_plugin replay_gain_filter_plugin = {
+ "replay_gain",
+ replay_gain_filter_init,
+};
+
+void
+replay_gain_filter_set_mixer(Filter *_filter, Mixer *mixer,
+ unsigned base)
+{
+ ReplayGainFilter *filter = (ReplayGainFilter *)_filter;
+
+ filter->SetMixer(mixer, base);
+}
+
+void
+replay_gain_filter_set_info(Filter *_filter, const replay_gain_info *info)
+{
+ ReplayGainFilter *filter = (ReplayGainFilter *)_filter;
+
+ filter->SetInfo(info);
+}
+
+void
+replay_gain_filter_set_mode(Filter *_filter, enum replay_gain_mode mode)
+{
+ ReplayGainFilter *filter = (ReplayGainFilter *)_filter;
+
+ filter->SetMode(mode);
+}
diff --git a/src/filter/ReplayGainFilterPlugin.hxx b/src/filter/ReplayGainFilterPlugin.hxx
new file mode 100644
index 000000000..06b778f8f
--- /dev/null
+++ b/src/filter/ReplayGainFilterPlugin.hxx
@@ -0,0 +1,52 @@
+/*
+ * 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_REPLAY_GAIN_FILTER_PLUGIN_HXX
+#define MPD_REPLAY_GAIN_FILTER_PLUGIN_HXX
+
+#include "replay_gain_info.h"
+
+class Filter;
+class Mixer;
+
+/**
+ * Enables or disables the hardware mixer for applying replay gain.
+ *
+ * @param mixer the hardware mixer, or NULL to fall back to software
+ * volume
+ * @param base the base volume level for scale=1.0, between 1 and 100
+ * (including).
+ */
+void
+replay_gain_filter_set_mixer(Filter *_filter, Mixer *mixer,
+ unsigned base);
+
+/**
+ * Sets a new #replay_gain_info at the beginning of a new song.
+ *
+ * @param info the new #replay_gain_info value, or NULL if no replay
+ * gain data is available for the current song
+ */
+void
+replay_gain_filter_set_info(Filter *filter, const replay_gain_info *info);
+
+void
+replay_gain_filter_set_mode(Filter *filter, enum replay_gain_mode mode);
+
+#endif
diff --git a/src/filter/RouteFilterPlugin.cxx b/src/filter/RouteFilterPlugin.cxx
new file mode 100644
index 000000000..ceb944822
--- /dev/null
+++ b/src/filter/RouteFilterPlugin.cxx
@@ -0,0 +1,322 @@
+/*
+ * 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
+ *
+ * This filter copies audio data between channels. Useful for
+ * upmixing mono/stereo audio to surround speaker configurations.
+ *
+ * Its configuration consists of a "filter" section with a single
+ * "routes" entry, formatted as: \\
+ * routes "0>1, 1>0, 2>2, 3>3, 3>4" \\
+ * where each pair of numbers signifies a set of channels.
+ * Each source>dest pair leads to the data from channel #source
+ * being copied to channel #dest in the output.
+ *
+ * Example: \\
+ * routes "0>0, 1>1, 0>2, 1>3"\\
+ * upmixes stereo audio to a 4-speaker system, copying the front-left
+ * (0) to front left (0) and rear left (2), copying front-right (1) to
+ * front-right (1) and rear-right (3).
+ *
+ * If multiple sources are copied to the same destination channel, only
+ * one of them takes effect.
+ */
+
+#include "config.h"
+#include "conf.h"
+#include "ConfigQuark.hxx"
+#include "AudioFormat.hxx"
+#include "CheckAudioFormat.hxx"
+#include "FilterPlugin.hxx"
+#include "FilterInternal.hxx"
+#include "FilterRegistry.hxx"
+#include "pcm/PcmBuffer.hxx"
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+
+class RouteFilter final : public Filter {
+ /**
+ * The minimum number of channels we need for output
+ * to be able to perform all the copies the user has specified
+ */
+ unsigned char min_output_channels;
+
+ /**
+ * The minimum number of input channels we need to
+ * copy all the data the user has requested. If fewer
+ * than this many are supplied by the input, undefined
+ * copy operations are given zeroed sources in stead.
+ */
+ unsigned char min_input_channels;
+
+ /**
+ * The set of copy operations to perform on each sample
+ * The index is an output channel to use, the value is
+ * a corresponding input channel from which to take the
+ * data. A -1 means "no source"
+ */
+ signed char* sources;
+
+ /**
+ * The actual input format of our signal, once opened
+ */
+ AudioFormat input_format;
+
+ /**
+ * The decided upon output format, once opened
+ */
+ AudioFormat output_format;
+
+ /**
+ * The size, in bytes, of each multichannel frame in the
+ * input buffer
+ */
+ size_t input_frame_size;
+
+ /**
+ * The size, in bytes, of each multichannel frame in the
+ * output buffer
+ */
+ size_t output_frame_size;
+
+ /**
+ * The output buffer used last time around, can be reused if the size doesn't differ.
+ */
+ PcmBuffer output_buffer;
+
+public:
+ RouteFilter():sources(nullptr) {}
+ ~RouteFilter() {
+ g_free(sources);
+ }
+
+ /**
+ * Parse the "routes" section, a string on the form
+ * a>b, c>d, e>f, ...
+ * where a... are non-unique, non-negative integers
+ * and input channel a gets copied to output channel b, etc.
+ * @param param the configuration block to read
+ * @param filter a route_filter whose min_channels and sources[] to set
+ * @return true on success, false on error
+ */
+ bool Configure(const config_param &param, GError **error_r);
+
+ virtual AudioFormat Open(AudioFormat &af, GError **error_r) override;
+ virtual void Close();
+ virtual const void *FilterPCM(const void *src, size_t src_size,
+ size_t *dest_size_r, GError **error_r);
+};
+
+bool
+RouteFilter::Configure(const config_param &param, GError **error_r) {
+
+ /* TODO:
+ * With a more clever way of marking "don't copy to output N",
+ * This could easily be merged into a single loop with some
+ * dynamic g_realloc() instead of one count run and one g_malloc().
+ */
+
+ gchar **tokens;
+ int number_of_copies;
+
+ // A cowardly default, just passthrough stereo
+ const char *const routes = param.GetBlockValue("routes", "0>0, 1>1");
+
+ min_input_channels = 0;
+ min_output_channels = 0;
+
+ tokens = g_strsplit(routes, ",", 255);
+ number_of_copies = g_strv_length(tokens);
+
+ // Start by figuring out a few basic things about the routing set
+ for (int c=0; c<number_of_copies; ++c) {
+
+ // String and int representations of the source/destination
+ gchar **sd;
+ int source, dest;
+
+ // Squeeze whitespace
+ g_strstrip(tokens[c]);
+
+ // Split the a>b string into source and destination
+ sd = g_strsplit(tokens[c], ">", 2);
+ if (g_strv_length(sd) != 2) {
+ g_set_error(error_r, config_quark(), 1,
+ "Invalid copy around %d in routes spec: %s",
+ param.line, tokens[c]);
+ g_strfreev(sd);
+ g_strfreev(tokens);
+ return false;
+ }
+
+ source = strtol(sd[0], NULL, 10);
+ dest = strtol(sd[1], NULL, 10);
+
+ // Keep track of the highest channel numbers seen
+ // as either in- or outputs
+ if (source >= min_input_channels)
+ min_input_channels = source + 1;
+ if (dest >= min_output_channels)
+ min_output_channels = dest + 1;
+
+ g_strfreev(sd);
+ }
+
+ if (!audio_valid_channel_count(min_output_channels)) {
+ g_strfreev(tokens);
+ g_set_error(error_r, audio_format_quark(), 0,
+ "Invalid number of output channels requested: %d",
+ min_output_channels);
+ return false;
+ }
+
+ // Allocate a map of "copy nothing to me"
+ sources = (signed char *)
+ g_malloc(min_output_channels * sizeof(signed char));
+
+ for (int i=0; i<min_output_channels; ++i)
+ sources[i] = -1;
+
+ // Run through the spec again, and save the
+ // actual mapping output <- input
+ for (int c=0; c<number_of_copies; ++c) {
+
+ // String and int representations of the source/destination
+ gchar **sd;
+ int source, dest;
+
+ // Split the a>b string into source and destination
+ sd = g_strsplit(tokens[c], ">", 2);
+ if (g_strv_length(sd) != 2) {
+ g_set_error(error_r, config_quark(), 1,
+ "Invalid copy around %d in routes spec: %s",
+ param.line, tokens[c]);
+ g_strfreev(sd);
+ g_strfreev(tokens);
+ return false;
+ }
+
+ source = strtol(sd[0], NULL, 10);
+ dest = strtol(sd[1], NULL, 10);
+
+ sources[dest] = source;
+
+ g_strfreev(sd);
+ }
+
+ g_strfreev(tokens);
+
+ return true;
+}
+
+static Filter *
+route_filter_init(const config_param &param, GError **error_r)
+{
+ RouteFilter *filter = new RouteFilter();
+ if (!filter->Configure(param, error_r)) {
+ delete filter;
+ return nullptr;
+ }
+
+ return filter;
+}
+
+AudioFormat
+RouteFilter::Open(AudioFormat &audio_format, gcc_unused GError **error_r)
+{
+ // Copy the input format for later reference
+ input_format = audio_format;
+ input_frame_size = input_format.GetFrameSize();
+
+ // Decide on an output format which has enough channels,
+ // and is otherwise identical
+ output_format = audio_format;
+ output_format.channels = min_output_channels;
+
+ // Precalculate this simple value, to speed up allocation later
+ output_frame_size = output_format.GetFrameSize();
+
+ return output_format;
+}
+
+void
+RouteFilter::Close()
+{
+ output_buffer.Clear();
+}
+
+const void *
+RouteFilter::FilterPCM(const void *src, size_t src_size,
+ size_t *dest_size_r, gcc_unused GError **error_r)
+{
+ size_t number_of_frames = src_size / input_frame_size;
+
+ const size_t bytes_per_frame_per_channel = input_format.GetSampleSize();
+
+ // A moving pointer that always refers to channel 0 in the input, at the currently handled frame
+ const uint8_t *base_source = (const uint8_t *)src;
+
+ // A moving pointer that always refers to the currently filled channel of the currently handled frame, in the output
+ uint8_t *chan_destination;
+
+ // Grow our reusable buffer, if needed, and set the moving pointer
+ *dest_size_r = number_of_frames * output_frame_size;
+ chan_destination = (uint8_t *)output_buffer.Get(*dest_size_r);
+
+ // Perform our copy operations, with N input channels and M output channels
+ for (unsigned int s=0; s<number_of_frames; ++s) {
+
+ // Need to perform one copy per output channel
+ for (unsigned int c=0; c<min_output_channels; ++c) {
+ if (sources[c] == -1 ||
+ (unsigned)sources[c] >= input_format.channels) {
+ // No source for this destination output,
+ // give it zeroes as input
+ memset(chan_destination,
+ 0x00,
+ bytes_per_frame_per_channel);
+ } else {
+ // Get the data from channel sources[c]
+ // and copy it to the output
+ const uint8_t *data = base_source +
+ (sources[c] * bytes_per_frame_per_channel);
+ memcpy(chan_destination,
+ data,
+ bytes_per_frame_per_channel);
+ }
+ // Move on to the next output channel
+ chan_destination += bytes_per_frame_per_channel;
+ }
+
+
+ // Go on to the next N input samples
+ base_source += input_frame_size;
+ }
+
+ // Here it is, ladies and gentlemen! Rerouted data!
+ return (void *) output_buffer.buffer;
+}
+
+const struct filter_plugin route_filter_plugin = {
+ "route",
+ route_filter_init,
+};
diff --git a/src/filter/VolumeFilterPlugin.cxx b/src/filter/VolumeFilterPlugin.cxx
new file mode 100644
index 000000000..fc7120c2d
--- /dev/null
+++ b/src/filter/VolumeFilterPlugin.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 "VolumeFilterPlugin.hxx"
+#include "FilterPlugin.hxx"
+#include "FilterInternal.hxx"
+#include "FilterRegistry.hxx"
+#include "conf.h"
+#include "pcm/PcmVolume.hxx"
+#include "pcm/PcmBuffer.hxx"
+#include "AudioFormat.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+class VolumeFilter final : public Filter {
+ /**
+ * The current volume, from 0 to #PCM_VOLUME_1.
+ */
+ unsigned volume;
+
+ AudioFormat format;
+
+ PcmBuffer buffer;
+
+public:
+ VolumeFilter()
+ :volume(PCM_VOLUME_1) {}
+
+ unsigned GetVolume() const {
+ assert(volume <= PCM_VOLUME_1);
+
+ return volume;
+ }
+
+ void SetVolume(unsigned _volume) {
+ assert(_volume <= PCM_VOLUME_1);
+
+ volume = _volume;
+ }
+
+ virtual AudioFormat Open(AudioFormat &af, GError **error_r) override;
+ virtual void Close();
+ virtual const void *FilterPCM(const void *src, size_t src_size,
+ size_t *dest_size_r, GError **error_r);
+};
+
+static inline GQuark
+volume_quark(void)
+{
+ return g_quark_from_static_string("pcm_volume");
+}
+
+static Filter *
+volume_filter_init(gcc_unused const config_param &param,
+ gcc_unused GError **error_r)
+{
+ return new VolumeFilter();
+}
+
+AudioFormat
+VolumeFilter::Open(AudioFormat &audio_format, gcc_unused GError **error_r)
+{
+ format = audio_format;
+
+ return format;
+}
+
+void
+VolumeFilter::Close()
+{
+ buffer.Clear();
+}
+
+const void *
+VolumeFilter::FilterPCM(const void *src, size_t src_size,
+ size_t *dest_size_r, GError **error_r)
+{
+ *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) {
+ g_set_error(error_r, volume_quark(), 0,
+ "pcm_volume() has failed");
+ return NULL;
+ }
+
+ return dest;
+}
+
+const struct filter_plugin volume_filter_plugin = {
+ "volume",
+ volume_filter_init,
+};
+
+unsigned
+volume_filter_get(const Filter *_filter)
+{
+ const VolumeFilter *filter =
+ (const VolumeFilter *)_filter;
+
+ return filter->GetVolume();
+}
+
+void
+volume_filter_set(Filter *_filter, unsigned volume)
+{
+ VolumeFilter *filter = (VolumeFilter *)_filter;
+
+ filter->SetVolume(volume);
+}
+
diff --git a/src/filter/VolumeFilterPlugin.hxx b/src/filter/VolumeFilterPlugin.hxx
new file mode 100644
index 000000000..822b7e93a
--- /dev/null
+++ b/src/filter/VolumeFilterPlugin.hxx
@@ -0,0 +1,31 @@
+/*
+ * 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_VOLUME_FILTER_PLUGIN_HXX
+#define MPD_VOLUME_FILTER_PLUGIN_HXX
+
+class Filter;
+
+unsigned
+volume_filter_get(const Filter *filter);
+
+void
+volume_filter_set(Filter *filter, unsigned volume);
+
+#endif
diff --git a/src/filter/autoconvert_filter_plugin.c b/src/filter/autoconvert_filter_plugin.c
deleted file mode 100644
index 3826a0fb3..000000000
--- a/src/filter/autoconvert_filter_plugin.c
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "filter/autoconvert_filter_plugin.h"
-#include "filter/convert_filter_plugin.h"
-#include "filter_plugin.h"
-#include "filter_internal.h"
-#include "filter_registry.h"
-#include "conf.h"
-#include "pcm_convert.h"
-#include "audio_format.h"
-#include "poison.h"
-
-#include <assert.h>
-#include <string.h>
-
-struct autoconvert_filter {
- struct filter base;
-
- /**
- * The audio format being fed to the underlying filter. This
- * plugin actually doesn't need this variable, we have it here
- * just so our open() method doesn't return a stack pointer.
- */
- struct audio_format in_audio_format;
-
- /**
- * The underlying filter.
- */
- struct filter *filter;
-
- /**
- * A convert_filter, just in case conversion is needed. NULL
- * if unused.
- */
- struct filter *convert;
-};
-
-static void
-autoconvert_filter_finish(struct filter *_filter)
-{
- struct autoconvert_filter *filter =
- (struct autoconvert_filter *)_filter;
-
- filter_free(filter->filter);
- g_free(filter);
-}
-
-static const struct audio_format *
-autoconvert_filter_open(struct filter *_filter,
- struct audio_format *in_audio_format,
- GError **error_r)
-{
- struct autoconvert_filter *filter =
- (struct autoconvert_filter *)_filter;
- const struct audio_format *out_audio_format;
-
- assert(audio_format_valid(in_audio_format));
-
- /* open the "real" filter */
-
- filter->in_audio_format = *in_audio_format;
-
- out_audio_format = filter_open(filter->filter,
- &filter->in_audio_format, error_r);
- if (out_audio_format == NULL)
- return NULL;
-
- /* need to convert? */
-
- if (!audio_format_equals(&filter->in_audio_format, in_audio_format)) {
- /* yes - create a convert_filter */
- struct audio_format audio_format2 = *in_audio_format;
- const struct audio_format *audio_format3;
-
- filter->convert = filter_new(&convert_filter_plugin, NULL,
- error_r);
- if (filter->convert == NULL) {
- filter_close(filter->filter);
- return NULL;
- }
-
- audio_format3 = filter_open(filter->convert, &audio_format2,
- error_r);
- if (audio_format3 == NULL) {
- filter_free(filter->convert);
- filter_close(filter->filter);
- return NULL;
- }
-
- assert(audio_format_equals(&audio_format2, in_audio_format));
-
- convert_filter_set(filter->convert, &filter->in_audio_format);
- } else
- /* no */
- filter->convert = NULL;
-
- return out_audio_format;
-}
-
-static void
-autoconvert_filter_close(struct filter *_filter)
-{
- struct autoconvert_filter *filter =
- (struct autoconvert_filter *)_filter;
-
- if (filter->convert != NULL) {
- filter_close(filter->convert);
- filter_free(filter->convert);
- }
-
- filter_close(filter->filter);
-}
-
-static const void *
-autoconvert_filter_filter(struct filter *_filter, const void *src,
- size_t src_size, size_t *dest_size_r,
- GError **error_r)
-{
- struct autoconvert_filter *filter =
- (struct autoconvert_filter *)_filter;
-
- if (filter->convert != NULL) {
- src = filter_filter(filter->convert, src, src_size, &src_size,
- error_r);
- if (src == NULL)
- return NULL;
- }
-
- return filter_filter(filter->filter, src, src_size, dest_size_r,
- error_r);
-}
-
-static const struct filter_plugin autoconvert_filter_plugin = {
- .name = "convert",
- .finish = autoconvert_filter_finish,
- .open = autoconvert_filter_open,
- .close = autoconvert_filter_close,
- .filter = autoconvert_filter_filter,
-};
-
-struct filter *
-autoconvert_filter_new(struct filter *_filter)
-{
- struct autoconvert_filter *filter =
- g_new(struct autoconvert_filter, 1);
-
- filter_init(&filter->base, &autoconvert_filter_plugin);
- filter->filter = _filter;
-
- return &filter->base;
-}
diff --git a/src/filter/autoconvert_filter_plugin.h b/src/filter/autoconvert_filter_plugin.h
deleted file mode 100644
index def08ab7e..000000000
--- a/src/filter/autoconvert_filter_plugin.h
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * 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 AUTOCONVERT_FILTER_PLUGIN_H
-#define AUTOCONVERT_FILTER_PLUGIN_H
-
-struct filter;
-
-/**
- * Creates a new "autoconvert" filter. When opened, it ensures that
- * the input audio format isn't changed. If the underlying filter
- * requests a different format, it automatically creates a
- * convert_filter.
- */
-struct filter *
-autoconvert_filter_new(struct filter *filter);
-
-#endif
diff --git a/src/filter/chain_filter_plugin.c b/src/filter/chain_filter_plugin.c
deleted file mode 100644
index 2c785a36f..000000000
--- a/src/filter/chain_filter_plugin.c
+++ /dev/null
@@ -1,213 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "conf.h"
-#include "filter/chain_filter_plugin.h"
-#include "filter_plugin.h"
-#include "filter_internal.h"
-#include "filter_registry.h"
-#include "audio_format.h"
-
-#include <assert.h>
-
-struct filter_chain {
- /** the base class */
- struct filter base;
-
- GSList *children;
-};
-
-static inline GQuark
-filter_quark(void)
-{
- return g_quark_from_static_string("filter");
-}
-
-static struct filter *
-chain_filter_init(G_GNUC_UNUSED const struct config_param *param,
- G_GNUC_UNUSED GError **error_r)
-{
- struct filter_chain *chain = g_new(struct filter_chain, 1);
-
- filter_init(&chain->base, &chain_filter_plugin);
- chain->children = NULL;
-
- return &chain->base;
-}
-
-static void
-chain_free_child(gpointer data, G_GNUC_UNUSED gpointer user_data)
-{
- struct filter *filter = data;
-
- filter_free(filter);
-}
-
-static void
-chain_filter_finish(struct filter *_filter)
-{
- struct filter_chain *chain = (struct filter_chain *)_filter;
-
- g_slist_foreach(chain->children, chain_free_child, NULL);
- g_slist_free(chain->children);
-
- g_free(chain);
-}
-
-/**
- * Close all filters in the chain until #until is reached. #until
- * itself is not closed.
- */
-static void
-chain_close_until(struct filter_chain *chain, const struct filter *until)
-{
- GSList *i = chain->children;
- struct filter *filter;
-
- while (true) {
- /* this assertion fails if #until does not exist
- (anymore) */
- assert(i != NULL);
-
- if (i->data == until)
- /* don't close this filter */
- break;
-
- /* close this filter */
- filter = i->data;
- filter_close(filter);
-
- i = g_slist_next(i);
- }
-}
-
-static const struct audio_format *
-chain_open_child(struct filter *filter,
- const struct audio_format *prev_audio_format,
- GError **error_r)
-{
- struct audio_format conv_audio_format = *prev_audio_format;
- const struct audio_format *next_audio_format;
-
- next_audio_format = filter_open(filter, &conv_audio_format, error_r);
- if (next_audio_format == NULL)
- return NULL;
-
- if (!audio_format_equals(&conv_audio_format, prev_audio_format)) {
- struct audio_format_string s;
-
- filter_close(filter);
- g_set_error(error_r, filter_quark(), 0,
- "Audio format not supported by filter '%s': %s",
- filter->plugin->name,
- audio_format_to_string(prev_audio_format, &s));
- return NULL;
- }
-
- return next_audio_format;
-}
-
-static const struct audio_format *
-chain_filter_open(struct filter *_filter, struct audio_format *in_audio_format,
- GError **error_r)
-{
- struct filter_chain *chain = (struct filter_chain *)_filter;
- const struct audio_format *audio_format = in_audio_format;
-
- for (GSList *i = chain->children; i != NULL; i = g_slist_next(i)) {
- struct filter *filter = i->data;
-
- audio_format = chain_open_child(filter, audio_format, error_r);
- if (audio_format == NULL) {
- /* rollback, close all children */
- chain_close_until(chain, filter);
- return NULL;
- }
- }
-
- /* return the output format of the last filter */
- return audio_format;
-}
-
-static void
-chain_close_child(gpointer data, G_GNUC_UNUSED gpointer user_data)
-{
- struct filter *filter = data;
-
- filter_close(filter);
-}
-
-static void
-chain_filter_close(struct filter *_filter)
-{
- struct filter_chain *chain = (struct filter_chain *)_filter;
-
- g_slist_foreach(chain->children, chain_close_child, NULL);
-}
-
-static const void *
-chain_filter_filter(struct filter *_filter,
- const void *src, size_t src_size,
- size_t *dest_size_r, GError **error_r)
-{
- struct filter_chain *chain = (struct filter_chain *)_filter;
-
- for (GSList *i = chain->children; i != NULL; i = g_slist_next(i)) {
- struct filter *filter = i->data;
-
- /* feed the output of the previous filter as input
- into the current one */
- src = filter_filter(filter, src, src_size, &src_size, error_r);
- if (src == NULL)
- return NULL;
- }
-
- /* return the output of the last filter */
- *dest_size_r = src_size;
- return src;
-}
-
-const struct filter_plugin chain_filter_plugin = {
- .name = "chain",
- .init = chain_filter_init,
- .finish = chain_filter_finish,
- .open = chain_filter_open,
- .close = chain_filter_close,
- .filter = chain_filter_filter,
-};
-
-struct filter *
-filter_chain_new(void)
-{
- struct filter *filter = filter_new(&chain_filter_plugin, NULL, NULL);
- /* chain_filter_init() never fails */
- assert(filter != NULL);
-
- return filter;
-}
-
-void
-filter_chain_append(struct filter *_chain, struct filter *filter)
-{
- struct filter_chain *chain = (struct filter_chain *)_chain;
-
- chain->children = g_slist_append(chain->children, filter);
-}
-
diff --git a/src/filter/chain_filter_plugin.h b/src/filter/chain_filter_plugin.h
deleted file mode 100644
index 1dba46667..000000000
--- a/src/filter/chain_filter_plugin.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * 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.
- */
-
-/** \file
- *
- * A filter chain is a container for several filters. They are
- * chained together, i.e. called in a row, one filter passing its
- * output to the next one.
- */
-
-#ifndef MPD_FILTER_CHAIN_H
-#define MPD_FILTER_CHAIN_H
-
-struct filter;
-
-/**
- * Creates a new filter chain.
- */
-struct filter *
-filter_chain_new(void);
-
-/**
- * Appends a new filter at the end of the filter chain. You must call
- * this function before the first filter_open() call.
- *
- * @param chain the filter chain created with filter_chain_new()
- * @param filter the filter to be appended to #chain
- */
-void
-filter_chain_append(struct filter *chain, struct filter *filter);
-
-#endif
diff --git a/src/filter/convert_filter_plugin.c b/src/filter/convert_filter_plugin.c
deleted file mode 100644
index c55b69af2..000000000
--- a/src/filter/convert_filter_plugin.c
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "filter/convert_filter_plugin.h"
-#include "filter_plugin.h"
-#include "filter_internal.h"
-#include "filter_registry.h"
-#include "conf.h"
-#include "pcm_convert.h"
-#include "audio_format.h"
-#include "poison.h"
-
-#include <assert.h>
-#include <string.h>
-
-struct convert_filter {
- struct filter base;
-
- /**
- * The current convert, from 0 to #PCM_CONVERT_1.
- */
- unsigned convert;
-
- /**
- * The input audio format; PCM data is passed to the filter()
- * method in this format.
- */
- struct audio_format in_audio_format;
-
- /**
- * 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().
- */
- struct audio_format out_audio_format;
-
- struct pcm_convert_state state;
-};
-
-static struct filter *
-convert_filter_init(G_GNUC_UNUSED const struct config_param *param,
- G_GNUC_UNUSED GError **error_r)
-{
- struct convert_filter *filter = g_new(struct convert_filter, 1);
-
- filter_init(&filter->base, &convert_filter_plugin);
- return &filter->base;
-}
-
-static void
-convert_filter_finish(struct filter *filter)
-{
- g_free(filter);
-}
-
-static const struct audio_format *
-convert_filter_open(struct filter *_filter, struct audio_format *audio_format,
- G_GNUC_UNUSED GError **error_r)
-{
- struct convert_filter *filter = (struct convert_filter *)_filter;
-
- assert(audio_format_valid(audio_format));
-
- filter->in_audio_format = filter->out_audio_format = *audio_format;
- pcm_convert_init(&filter->state);
-
- return &filter->in_audio_format;
-}
-
-static void
-convert_filter_close(struct filter *_filter)
-{
- struct convert_filter *filter = (struct convert_filter *)_filter;
-
- pcm_convert_deinit(&filter->state);
-
- poison_undefined(&filter->in_audio_format,
- sizeof(filter->in_audio_format));
- poison_undefined(&filter->out_audio_format,
- sizeof(filter->out_audio_format));
-}
-
-static const void *
-convert_filter_filter(struct filter *_filter, const void *src, size_t src_size,
- size_t *dest_size_r, GError **error_r)
-{
- struct convert_filter *filter = (struct convert_filter *)_filter;
- const void *dest;
-
- if (audio_format_equals(&filter->in_audio_format,
- &filter->out_audio_format)) {
- /* optimized special case: no-op */
- *dest_size_r = src_size;
- return src;
- }
-
- dest = pcm_convert(&filter->state, &filter->in_audio_format,
- src, src_size,
- &filter->out_audio_format, dest_size_r,
- error_r);
- if (dest == NULL)
- return NULL;
-
- return dest;
-}
-
-const struct filter_plugin convert_filter_plugin = {
- .name = "convert",
- .init = convert_filter_init,
- .finish = convert_filter_finish,
- .open = convert_filter_open,
- .close = convert_filter_close,
- .filter = convert_filter_filter,
-};
-
-void
-convert_filter_set(struct filter *_filter,
- const struct audio_format *out_audio_format)
-{
- struct convert_filter *filter = (struct convert_filter *)_filter;
-
- assert(filter != NULL);
- assert(audio_format_valid(&filter->in_audio_format));
- assert(audio_format_valid(&filter->out_audio_format));
- assert(out_audio_format != NULL);
- assert(audio_format_valid(out_audio_format));
-
- filter->out_audio_format = *out_audio_format;
-}
diff --git a/src/filter/convert_filter_plugin.h b/src/filter/convert_filter_plugin.h
deleted file mode 100644
index 156adf8e3..000000000
--- a/src/filter/convert_filter_plugin.h
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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 CONVERT_FILTER_PLUGIN_H
-#define CONVERT_FILTER_PLUGIN_H
-
-struct filter;
-struct audio_format;
-
-/**
- * Sets the output audio format for the specified filter. You must
- * call this after the filter has been opened. Since this audio
- * format switch is a violation of the filter API, this filter must be
- * the last in a chain.
- */
-void
-convert_filter_set(struct filter *filter,
- const struct audio_format *out_audio_format);
-
-#endif
diff --git a/src/filter/normalize_filter_plugin.c b/src/filter/normalize_filter_plugin.c
deleted file mode 100644
index 2151482e4..000000000
--- a/src/filter/normalize_filter_plugin.c
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "filter_plugin.h"
-#include "filter_internal.h"
-#include "filter_registry.h"
-#include "pcm_buffer.h"
-#include "audio_format.h"
-#include "AudioCompress/compress.h"
-
-#include <assert.h>
-#include <string.h>
-
-struct normalize_filter {
- struct filter filter;
-
- struct Compressor *compressor;
-
- struct pcm_buffer buffer;
-};
-
-static inline GQuark
-normalize_quark(void)
-{
- return g_quark_from_static_string("normalize");
-}
-
-static struct filter *
-normalize_filter_init(G_GNUC_UNUSED const struct config_param *param,
- G_GNUC_UNUSED GError **error_r)
-{
- struct normalize_filter *filter = g_new(struct normalize_filter, 1);
-
- filter_init(&filter->filter, &normalize_filter_plugin);
-
- return &filter->filter;
-}
-
-static void
-normalize_filter_finish(struct filter *filter)
-{
- g_free(filter);
-}
-
-static const struct audio_format *
-normalize_filter_open(struct filter *_filter,
- struct audio_format *audio_format,
- G_GNUC_UNUSED GError **error_r)
-{
- struct normalize_filter *filter = (struct normalize_filter *)_filter;
-
- audio_format->format = SAMPLE_FORMAT_S16;
-
- filter->compressor = Compressor_new(0);
-
- pcm_buffer_init(&filter->buffer);
-
- return audio_format;
-}
-
-static void
-normalize_filter_close(struct filter *_filter)
-{
- struct normalize_filter *filter = (struct normalize_filter *)_filter;
-
- pcm_buffer_deinit(&filter->buffer);
- Compressor_delete(filter->compressor);
-}
-
-static const void *
-normalize_filter_filter(struct filter *_filter,
- const void *src, size_t src_size, size_t *dest_size_r,
- G_GNUC_UNUSED GError **error_r)
-{
- struct normalize_filter *filter = (struct normalize_filter *)_filter;
- void *dest;
-
- dest = pcm_buffer_get(&filter->buffer, src_size);
-
- memcpy(dest, src, src_size);
-
- Compressor_Process_int16(filter->compressor, dest, src_size / 2);
-
- *dest_size_r = src_size;
- return dest;
-}
-
-const struct filter_plugin normalize_filter_plugin = {
- .name = "normalize",
- .init = normalize_filter_init,
- .finish = normalize_filter_finish,
- .open = normalize_filter_open,
- .close = normalize_filter_close,
- .filter = normalize_filter_filter,
-};
diff --git a/src/filter/null_filter_plugin.c b/src/filter/null_filter_plugin.c
deleted file mode 100644
index e7c998827..000000000
--- a/src/filter/null_filter_plugin.c
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * 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.
- */
-
-/** \file
- *
- * This filter plugin does nothing. That is not quite useful, except
- * for testing the filter core, or as a template for new filter
- * plugins.
- */
-
-#include "config.h"
-#include "filter_plugin.h"
-#include "filter_internal.h"
-#include "filter_registry.h"
-
-#include <assert.h>
-
-struct null_filter {
- struct filter filter;
-};
-
-static struct filter *
-null_filter_init(G_GNUC_UNUSED const struct config_param *param,
- G_GNUC_UNUSED GError **error_r)
-{
- struct null_filter *filter = g_new(struct null_filter, 1);
-
- filter_init(&filter->filter, &null_filter_plugin);
- return &filter->filter;
-}
-
-static void
-null_filter_finish(struct filter *_filter)
-{
- struct null_filter *filter = (struct null_filter *)_filter;
-
- g_free(filter);
-}
-
-static const struct audio_format *
-null_filter_open(struct filter *_filter, struct audio_format *audio_format,
- G_GNUC_UNUSED GError **error_r)
-{
- struct null_filter *filter = (struct null_filter *)_filter;
- (void)filter;
-
- return audio_format;
-}
-
-static void
-null_filter_close(struct filter *_filter)
-{
- struct null_filter *filter = (struct null_filter *)_filter;
- (void)filter;
-}
-
-static const void *
-null_filter_filter(struct filter *_filter,
- const void *src, size_t src_size,
- size_t *dest_size_r, G_GNUC_UNUSED GError **error_r)
-{
- struct null_filter *filter = (struct null_filter *)_filter;
- (void)filter;
-
- /* return the unmodified source buffer */
- *dest_size_r = src_size;
- return src;
-}
-
-const struct filter_plugin null_filter_plugin = {
- .name = "null",
- .init = null_filter_init,
- .finish = null_filter_finish,
- .open = null_filter_open,
- .close = null_filter_close,
- .filter = null_filter_filter,
-};
diff --git a/src/filter/replay_gain_filter_plugin.c b/src/filter/replay_gain_filter_plugin.c
deleted file mode 100644
index 583a09f90..000000000
--- a/src/filter/replay_gain_filter_plugin.c
+++ /dev/null
@@ -1,245 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "filter/replay_gain_filter_plugin.h"
-#include "filter_plugin.h"
-#include "filter_internal.h"
-#include "filter_registry.h"
-#include "audio_format.h"
-#include "pcm_buffer.h"
-#include "pcm_volume.h"
-#include "replay_gain_info.h"
-#include "replay_gain_config.h"
-#include "mixer_control.h"
-
-#include <assert.h>
-#include <string.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "replay_gain"
-
-struct replay_gain_filter {
- struct filter filter;
-
- /**
- * If set, then this hardware mixer is used for applying
- * replay gain, instead of the software volume library.
- */
- struct mixer *mixer;
-
- /**
- * The base volume level for scale=1.0, between 1 and 100
- * (including).
- */
- unsigned base;
-
- enum replay_gain_mode mode;
-
- struct replay_gain_info info;
-
- /**
- * The current volume, 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.
- *
- * On the other hand, if the user has set replaygain_limit to false,
- * the chance of clipping is explicitly preferred if that's required to
- * maintain a consistent audio level. Whether clipping will actually
- * occur depends on what value the user is using for replaygain_preamp.
- */
- unsigned volume;
-
- struct audio_format audio_format;
-
- struct pcm_buffer buffer;
-};
-
-static inline GQuark
-replay_gain_quark(void)
-{
- return g_quark_from_static_string("replay_gain");
-}
-
-/**
- * Recalculates the new volume after a property was changed.
- */
-static void
-replay_gain_filter_update(struct replay_gain_filter *filter)
-{
- if (filter->mode != REPLAY_GAIN_OFF) {
- float scale = replay_gain_tuple_scale(&filter->info.tuples[filter->mode],
- replay_gain_preamp, replay_gain_missing_preamp, replay_gain_limit);
- g_debug("scale=%f\n", (double)scale);
-
- filter->volume = pcm_float_to_volume(scale);
- } else
- filter->volume = PCM_VOLUME_1;
-
- if (filter->mixer != NULL) {
- /* update the hardware mixer volume */
-
- unsigned volume = (filter->volume * filter->base) / PCM_VOLUME_1;
- if (volume > 100)
- volume = 100;
-
- GError *error = NULL;
- if (!mixer_set_volume(filter->mixer, volume, &error)) {
- g_warning("Failed to update hardware mixer: %s",
- error->message);
- g_error_free(error);
- }
- }
-}
-
-static struct filter *
-replay_gain_filter_init(G_GNUC_UNUSED const struct config_param *param,
- G_GNUC_UNUSED GError **error_r)
-{
- struct replay_gain_filter *filter = g_new(struct replay_gain_filter, 1);
-
- filter_init(&filter->filter, &replay_gain_filter_plugin);
- filter->mixer = NULL;
-
- filter->mode = replay_gain_get_real_mode();
- replay_gain_info_init(&filter->info);
- filter->volume = PCM_VOLUME_1;
-
- return &filter->filter;
-}
-
-static void
-replay_gain_filter_finish(struct filter *filter)
-{
- g_free(filter);
-}
-
-static const struct audio_format *
-replay_gain_filter_open(struct filter *_filter,
- struct audio_format *audio_format,
- G_GNUC_UNUSED GError **error_r)
-{
- struct replay_gain_filter *filter =
- (struct replay_gain_filter *)_filter;
-
- filter->audio_format = *audio_format;
- pcm_buffer_init(&filter->buffer);
-
- return &filter->audio_format;
-}
-
-static void
-replay_gain_filter_close(struct filter *_filter)
-{
- struct replay_gain_filter *filter =
- (struct replay_gain_filter *)_filter;
-
- pcm_buffer_deinit(&filter->buffer);
-}
-
-static const void *
-replay_gain_filter_filter(struct filter *_filter,
- const void *src, size_t src_size,
- size_t *dest_size_r, GError **error_r)
-{
- struct replay_gain_filter *filter =
- (struct replay_gain_filter *)_filter;
- bool success;
- void *dest;
- enum replay_gain_mode rg_mode;
-
- /* check if the mode has been changed since the last call */
- rg_mode = replay_gain_get_real_mode();
-
- if (filter->mode != rg_mode) {
- g_debug("replay gain mode has changed %d->%d\n", filter->mode, rg_mode);
- filter->mode = rg_mode;
- replay_gain_filter_update(filter);
- }
-
- *dest_size_r = src_size;
-
- if (filter->volume == PCM_VOLUME_1)
- /* optimized special case: 100% volume = no-op */
- return src;
-
- dest = pcm_buffer_get(&filter->buffer, src_size);
-
- if (filter->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);
-
- success = pcm_volume(dest, src_size, filter->audio_format.format,
- filter->volume);
- if (!success) {
- g_set_error(error_r, replay_gain_quark(), 0,
- "pcm_volume() has failed");
- return NULL;
- }
-
- return dest;
-}
-
-const struct filter_plugin replay_gain_filter_plugin = {
- .name = "replay_gain",
- .init = replay_gain_filter_init,
- .finish = replay_gain_filter_finish,
- .open = replay_gain_filter_open,
- .close = replay_gain_filter_close,
- .filter = replay_gain_filter_filter,
-};
-
-void
-replay_gain_filter_set_mixer(struct filter *_filter, struct mixer *mixer,
- unsigned base)
-{
- struct replay_gain_filter *filter =
- (struct replay_gain_filter *)_filter;
-
- assert(mixer == NULL || (base > 0 && base <= 100));
-
- filter->mixer = mixer;
- filter->base = base;
-
- replay_gain_filter_update(filter);
-}
-
-void
-replay_gain_filter_set_info(struct filter *_filter,
- const struct replay_gain_info *info)
-{
- struct replay_gain_filter *filter =
- (struct replay_gain_filter *)_filter;
-
- if (info != NULL) {
- filter->info = *info;
- replay_gain_info_complete(&filter->info);
- } else
- replay_gain_info_init(&filter->info);
-
- replay_gain_filter_update(filter);
-}
diff --git a/src/filter/replay_gain_filter_plugin.h b/src/filter/replay_gain_filter_plugin.h
deleted file mode 100644
index 45b738e40..000000000
--- a/src/filter/replay_gain_filter_plugin.h
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * 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 REPLAY_GAIN_FILTER_PLUGIN_H
-#define REPLAY_GAIN_FILTER_PLUGIN_H
-
-#include "replay_gain_info.h"
-
-struct filter;
-struct mixer;
-
-/**
- * Enables or disables the hardware mixer for applying replay gain.
- *
- * @param mixer the hardware mixer, or NULL to fall back to software
- * volume
- * @param base the base volume level for scale=1.0, between 1 and 100
- * (including).
- */
-void
-replay_gain_filter_set_mixer(struct filter *_filter, struct mixer *mixer,
- unsigned base);
-
-/**
- * Sets a new #replay_gain_info at the beginning of a new song.
- *
- * @param info the new #replay_gain_info value, or NULL if no replay
- * gain data is available for the current song
- */
-void
-replay_gain_filter_set_info(struct filter *filter,
- const struct replay_gain_info *info);
-
-#endif
diff --git a/src/filter/route_filter_plugin.c b/src/filter/route_filter_plugin.c
deleted file mode 100644
index 3bf8677e5..000000000
--- a/src/filter/route_filter_plugin.c
+++ /dev/null
@@ -1,348 +0,0 @@
-/*
- * 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.
- */
-
-/** \file
- *
- * This filter copies audio data between channels. Useful for
- * upmixing mono/stereo audio to surround speaker configurations.
- *
- * Its configuration consists of a "filter" section with a single
- * "routes" entry, formatted as: \\
- * routes "0>1, 1>0, 2>2, 3>3, 3>4" \\
- * where each pair of numbers signifies a set of channels.
- * Each source>dest pair leads to the data from channel #source
- * being copied to channel #dest in the output.
- *
- * Example: \\
- * routes "0>0, 1>1, 0>2, 1>3"\\
- * upmixes stereo audio to a 4-speaker system, copying the front-left
- * (0) to front left (0) and rear left (2), copying front-right (1) to
- * front-right (1) and rear-right (3).
- *
- * If multiple sources are copied to the same destination channel, only
- * one of them takes effect.
- */
-
-#include "config.h"
-#include "conf.h"
-#include "audio_format.h"
-#include "audio_check.h"
-#include "filter_plugin.h"
-#include "filter_internal.h"
-#include "filter_registry.h"
-#include "pcm_buffer.h"
-
-#include <assert.h>
-#include <string.h>
-#include <stdlib.h>
-
-
-struct route_filter {
-
- /**
- * Inherit (and support cast to/from) filter
- */
- struct filter base;
-
- /**
- * The minimum number of channels we need for output
- * to be able to perform all the copies the user has specified
- */
- unsigned char min_output_channels;
-
- /**
- * The minimum number of input channels we need to
- * copy all the data the user has requested. If fewer
- * than this many are supplied by the input, undefined
- * copy operations are given zeroed sources in stead.
- */
- unsigned char min_input_channels;
-
- /**
- * The set of copy operations to perform on each sample
- * The index is an output channel to use, the value is
- * a corresponding input channel from which to take the
- * data. A -1 means "no source"
- */
- signed char* sources;
-
- /**
- * The actual input format of our signal, once opened
- */
- struct audio_format input_format;
-
- /**
- * The decided upon output format, once opened
- */
- struct audio_format output_format;
-
- /**
- * The size, in bytes, of each multichannel frame in the
- * input buffer
- */
- size_t input_frame_size;
-
- /**
- * The size, in bytes, of each multichannel frame in the
- * output buffer
- */
- size_t output_frame_size;
-
- /**
- * The output buffer used last time around, can be reused if the size doesn't differ.
- */
- struct pcm_buffer output_buffer;
-
-};
-
-/**
- * Parse the "routes" section, a string on the form
- * a>b, c>d, e>f, ...
- * where a... are non-unique, non-negative integers
- * and input channel a gets copied to output channel b, etc.
- * @param param the configuration block to read
- * @param filter a route_filter whose min_channels and sources[] to set
- * @return true on success, false on error
- */
-static bool
-route_filter_parse(const struct config_param *param,
- struct route_filter *filter,
- GError **error_r) {
-
- /* TODO:
- * With a more clever way of marking "don't copy to output N",
- * This could easily be merged into a single loop with some
- * dynamic g_realloc() instead of one count run and one g_malloc().
- */
-
- gchar **tokens;
- int number_of_copies;
-
- // A cowardly default, just passthrough stereo
- const char *routes =
- config_get_block_string(param, "routes", "0>0, 1>1");
-
- filter->min_input_channels = 0;
- filter->min_output_channels = 0;
-
- tokens = g_strsplit(routes, ",", 255);
- number_of_copies = g_strv_length(tokens);
-
- // Start by figuring out a few basic things about the routing set
- for (int c=0; c<number_of_copies; ++c) {
-
- // String and int representations of the source/destination
- gchar **sd;
- int source, dest;
-
- // Squeeze whitespace
- g_strstrip(tokens[c]);
-
- // Split the a>b string into source and destination
- sd = g_strsplit(tokens[c], ">", 2);
- if (g_strv_length(sd) != 2) {
- g_set_error(error_r, config_quark(), 1,
- "Invalid copy around %d in routes spec: %s",
- param->line, tokens[c]);
- g_strfreev(sd);
- g_strfreev(tokens);
- return false;
- }
-
- source = strtol(sd[0], NULL, 10);
- dest = strtol(sd[1], NULL, 10);
-
- // Keep track of the highest channel numbers seen
- // as either in- or outputs
- if (source >= filter->min_input_channels)
- filter->min_input_channels = source + 1;
- if (dest >= filter->min_output_channels)
- filter->min_output_channels = dest + 1;
-
- g_strfreev(sd);
- }
-
- if (!audio_valid_channel_count(filter->min_output_channels)) {
- g_strfreev(tokens);
- g_set_error(error_r, audio_format_quark(), 0,
- "Invalid number of output channels requested: %d",
- filter->min_output_channels);
- return false;
- }
-
- // Allocate a map of "copy nothing to me"
- filter->sources =
- g_malloc(filter->min_output_channels * sizeof(signed char));
-
- for (int i=0; i<filter->min_output_channels; ++i)
- filter->sources[i] = -1;
-
- // Run through the spec again, and save the
- // actual mapping output <- input
- for (int c=0; c<number_of_copies; ++c) {
-
- // String and int representations of the source/destination
- gchar **sd;
- int source, dest;
-
- // Split the a>b string into source and destination
- sd = g_strsplit(tokens[c], ">", 2);
- if (g_strv_length(sd) != 2) {
- g_set_error(error_r, config_quark(), 1,
- "Invalid copy around %d in routes spec: %s",
- param->line, tokens[c]);
- g_strfreev(sd);
- g_strfreev(tokens);
- return false;
- }
-
- source = strtol(sd[0], NULL, 10);
- dest = strtol(sd[1], NULL, 10);
-
- filter->sources[dest] = source;
-
- g_strfreev(sd);
- }
-
- g_strfreev(tokens);
-
- return true;
-}
-
-static struct filter *
-route_filter_init(const struct config_param *param,
- G_GNUC_UNUSED GError **error_r)
-{
- struct route_filter *filter = g_new(struct route_filter, 1);
- filter_init(&filter->base, &route_filter_plugin);
-
- // Allocate and set the filter->sources[] array
- route_filter_parse(param, filter, error_r);
-
- return &filter->base;
-}
-
-static void
-route_filter_finish(struct filter *_filter)
-{
- struct route_filter *filter = (struct route_filter *)_filter;
-
- g_free(filter->sources);
- g_free(filter);
-}
-
-static const struct audio_format *
-route_filter_open(struct filter *_filter, struct audio_format *audio_format,
- G_GNUC_UNUSED GError **error_r)
-{
- struct route_filter *filter = (struct route_filter *)_filter;
-
- // Copy the input format for later reference
- filter->input_format = *audio_format;
- filter->input_frame_size =
- audio_format_frame_size(&filter->input_format);
-
- // Decide on an output format which has enough channels,
- // and is otherwise identical
- filter->output_format = *audio_format;
- filter->output_format.channels = filter->min_output_channels;
-
- // Precalculate this simple value, to speed up allocation later
- filter->output_frame_size =
- audio_format_frame_size(&filter->output_format);
-
- // This buffer grows as needed
- pcm_buffer_init(&filter->output_buffer);
-
- return &filter->output_format;
-}
-
-static void
-route_filter_close(struct filter *_filter)
-{
- struct route_filter *filter = (struct route_filter *)_filter;
-
- pcm_buffer_deinit(&filter->output_buffer);
-}
-
-static const void *
-route_filter_filter(struct filter *_filter,
- const void *src, size_t src_size,
- size_t *dest_size_r, G_GNUC_UNUSED GError **error_r)
-{
- struct route_filter *filter = (struct route_filter *)_filter;
-
- size_t number_of_frames = src_size / filter->input_frame_size;
-
- size_t bytes_per_frame_per_channel =
- audio_format_sample_size(&filter->input_format);
-
- // A moving pointer that always refers to channel 0 in the input, at the currently handled frame
- const uint8_t *base_source = src;
-
- // A moving pointer that always refers to the currently filled channel of the currently handled frame, in the output
- uint8_t *chan_destination;
-
- // Grow our reusable buffer, if needed, and set the moving pointer
- *dest_size_r = number_of_frames * filter->output_frame_size;
- chan_destination = pcm_buffer_get(&filter->output_buffer, *dest_size_r);
-
-
- // Perform our copy operations, with N input channels and M output channels
- for (unsigned int s=0; s<number_of_frames; ++s) {
-
- // Need to perform one copy per output channel
- for (unsigned int c=0; c<filter->min_output_channels; ++c) {
- if (filter->sources[c] == -1 ||
- (unsigned)filter->sources[c] >= filter->input_format.channels) {
- // No source for this destination output,
- // give it zeroes as input
- memset(chan_destination,
- 0x00,
- bytes_per_frame_per_channel);
- } else {
- // Get the data from channel sources[c]
- // and copy it to the output
- const uint8_t *data = base_source +
- (filter->sources[c] * bytes_per_frame_per_channel);
- memcpy(chan_destination,
- data,
- bytes_per_frame_per_channel);
- }
- // Move on to the next output channel
- chan_destination += bytes_per_frame_per_channel;
- }
-
-
- // Go on to the next N input samples
- base_source += filter->input_frame_size;
- }
-
- // Here it is, ladies and gentlemen! Rerouted data!
- return (void *) filter->output_buffer.buffer;
-}
-
-const struct filter_plugin route_filter_plugin = {
- .name = "route",
- .init = route_filter_init,
- .finish = route_filter_finish,
- .open = route_filter_open,
- .close = route_filter_close,
- .filter = route_filter_filter,
-};
diff --git a/src/filter/volume_filter_plugin.c b/src/filter/volume_filter_plugin.c
deleted file mode 100644
index 3260e8989..000000000
--- a/src/filter/volume_filter_plugin.c
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "filter/volume_filter_plugin.h"
-#include "filter_plugin.h"
-#include "filter_internal.h"
-#include "filter_registry.h"
-#include "conf.h"
-#include "pcm_buffer.h"
-#include "pcm_volume.h"
-#include "audio_format.h"
-
-#include <assert.h>
-#include <string.h>
-
-struct volume_filter {
- struct filter filter;
-
- /**
- * The current volume, from 0 to #PCM_VOLUME_1.
- */
- unsigned volume;
-
- struct audio_format audio_format;
-
- struct pcm_buffer buffer;
-};
-
-static inline GQuark
-volume_quark(void)
-{
- return g_quark_from_static_string("pcm_volume");
-}
-
-static struct filter *
-volume_filter_init(G_GNUC_UNUSED const struct config_param *param,
- G_GNUC_UNUSED GError **error_r)
-{
- struct volume_filter *filter = g_new(struct volume_filter, 1);
-
- filter_init(&filter->filter, &volume_filter_plugin);
- filter->volume = PCM_VOLUME_1;
-
- return &filter->filter;
-}
-
-static void
-volume_filter_finish(struct filter *filter)
-{
- g_free(filter);
-}
-
-static const struct audio_format *
-volume_filter_open(struct filter *_filter, struct audio_format *audio_format,
- G_GNUC_UNUSED GError **error_r)
-{
- struct volume_filter *filter = (struct volume_filter *)_filter;
-
- filter->audio_format = *audio_format;
- pcm_buffer_init(&filter->buffer);
-
- return &filter->audio_format;
-}
-
-static void
-volume_filter_close(struct filter *_filter)
-{
- struct volume_filter *filter = (struct volume_filter *)_filter;
-
- pcm_buffer_deinit(&filter->buffer);
-}
-
-static const void *
-volume_filter_filter(struct filter *_filter, const void *src, size_t src_size,
- size_t *dest_size_r, GError **error_r)
-{
- struct volume_filter *filter = (struct volume_filter *)_filter;
- bool success;
- void *dest;
-
- *dest_size_r = src_size;
-
- if (filter->volume >= PCM_VOLUME_1)
- /* optimized special case: 100% volume = no-op */
- return src;
-
- dest = pcm_buffer_get(&filter->buffer, src_size);
-
- if (filter->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);
-
- success = pcm_volume(dest, src_size, filter->audio_format.format,
- filter->volume);
- if (!success) {
- g_set_error(error_r, volume_quark(), 0,
- "pcm_volume() has failed");
- return NULL;
- }
-
- return dest;
-}
-
-const struct filter_plugin volume_filter_plugin = {
- .name = "volume",
- .init = volume_filter_init,
- .finish = volume_filter_finish,
- .open = volume_filter_open,
- .close = volume_filter_close,
- .filter = volume_filter_filter,
-};
-
-unsigned
-volume_filter_get(const struct filter *_filter)
-{
- const struct volume_filter *filter =
- (const struct volume_filter *)_filter;
-
- assert(filter->filter.plugin == &volume_filter_plugin);
- assert(filter->volume <= PCM_VOLUME_1);
-
- return filter->volume;
-}
-
-void
-volume_filter_set(struct filter *_filter, unsigned volume)
-{
- struct volume_filter *filter = (struct volume_filter *)_filter;
-
- assert(filter->filter.plugin == &volume_filter_plugin);
- assert(volume <= PCM_VOLUME_1);
-
- filter->volume = volume;
-}
-
diff --git a/src/filter/volume_filter_plugin.h b/src/filter/volume_filter_plugin.h
deleted file mode 100644
index 5b16f7e57..000000000
--- a/src/filter/volume_filter_plugin.h
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * 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 VOLUME_FILTER_PLUGIN_H
-#define VOLUME_FILTER_PLUGIN_H
-
-struct filter;
-
-unsigned
-volume_filter_get(const struct filter *filter);
-
-void
-volume_filter_set(struct filter *filter, unsigned volume);
-
-#endif
diff --git a/src/filter_config.c b/src/filter_config.c
deleted file mode 100644
index ab9bdb0c5..000000000
--- a/src/filter_config.c
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * 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.
- */
-
-#include "filter_config.h"
-#include "config.h"
-#include "conf.h"
-#include "filter/chain_filter_plugin.h"
-#include "filter_plugin.h"
-#include "filter_internal.h"
-#include "filter_registry.h"
-
-#include <string.h>
-
-
-static GQuark
-filter_quark(void)
-{
- return g_quark_from_static_string("filter");
-}
-
-/**
- * Find the "filter" configuration block for the specified name.
- *
- * @param filter_template_name the name of the filter template
- * @param error_r space to return an error description
- * @return the configuration block, or NULL if none was configured
- */
-static const struct config_param *
-filter_plugin_config(const char *filter_template_name, GError **error_r)
-{
- const struct config_param *param = NULL;
-
- while ((param = config_get_next_param(CONF_AUDIO_FILTER, param)) != NULL) {
- const char *name =
- config_get_block_string(param, "name", NULL);
- if (name == NULL) {
- g_set_error(error_r, filter_quark(), 1,
- "filter configuration without 'name' name in line %d",
- param->line);
- return NULL;
- }
-
- if (strcmp(name, filter_template_name) == 0)
- return param;
- }
-
- g_set_error(error_r, filter_quark(), 1,
- "filter template not found: %s",
- filter_template_name);
-
- return NULL;
-}
-
-/**
- * Builds a filter chain from a configuration string on the form
- * "name1, name2, name3, ..." by looking up each name among the
- * configured filter sections.
- * @param chain the chain to append filters on
- * @param spec the filter chain specification
- * @param error_r space to return an error description
- * @return the number of filters which were successfully added
- */
-unsigned int
-filter_chain_parse(struct filter *chain, const char *spec, GError **error_r)
-{
-
- // Split on comma
- gchar** tokens = g_strsplit_set(spec, ",", 255);
-
- int added_filters = 0;
-
- // Add each name to the filter chain by instantiating an actual filter
- char **template_names = tokens;
- while (*template_names != NULL) {
- struct filter *f;
- const struct config_param *cfg;
-
- // Squeeze whitespace
- g_strstrip(*template_names);
-
- cfg = filter_plugin_config(*template_names, error_r);
- if (cfg == NULL) {
- // The error has already been set, just stop.
- break;
- }
-
- // Instantiate one of those filter plugins with the template name as a hint
- f = filter_configured_new(cfg, error_r);
- if (f == NULL) {
- // The error has already been set, just stop.
- break;
- }
-
- filter_chain_append(chain, f);
- ++added_filters;
-
- ++template_names;
- }
-
- g_strfreev(tokens);
-
- return added_filters;
-}
diff --git a/src/filter_config.h b/src/filter_config.h
deleted file mode 100644
index 920cbc07c..000000000
--- a/src/filter_config.h
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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.
- */
-
-/** \file
- *
- * Utility functions for filter configuration
- */
-
-#ifndef MPD_FILTER_CONFIG_H
-#define MPD_FILTER_CONFIG_H
-
-#include "conf.h"
-#include "filter/chain_filter_plugin.h"
-#include "filter_plugin.h"
-#include "filter_internal.h"
-#include "filter_registry.h"
-
-
-/**
- * Builds a filter chain from a configuration string on the form
- * "name1, name2, name3, ..." by looking up each name among the
- * configured filter sections.
- * @param chain the chain to append filters on
- * @param spec the filter chain specification
- * @param error_r space to return an error description
- * @return the number of filters which were successfully added
- */
-unsigned int
-filter_chain_parse(struct filter *chain, const char *spec, GError **error_r);
-
-#endif
diff --git a/src/filter_internal.h b/src/filter_internal.h
deleted file mode 100644
index 4e94599a2..000000000
--- a/src/filter_internal.h
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * 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.
- */
-
-/** \file
- *
- * Internal stuff for the filter core and filter plugins.
- */
-
-#ifndef MPD_FILTER_INTERNAL_H
-#define MPD_FILTER_INTERNAL_H
-
-struct filter {
- const struct filter_plugin *plugin;
-};
-
-static inline void
-filter_init(struct filter *filter, const struct filter_plugin *plugin)
-{
- filter->plugin = plugin;
-}
-
-#endif
diff --git a/src/filter_plugin.c b/src/filter_plugin.c
deleted file mode 100644
index 7173134b3..000000000
--- a/src/filter_plugin.c
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "filter_plugin.h"
-#include "filter_internal.h"
-#include "filter_registry.h"
-#include "conf.h"
-
-#ifndef NDEBUG
-#include "audio_format.h"
-#endif
-
-#include <assert.h>
-
-struct filter *
-filter_new(const struct filter_plugin *plugin,
- const struct config_param *param, GError **error_r)
-{
- assert(plugin != NULL);
- assert(error_r == NULL || *error_r == NULL);
-
- return plugin->init(param, error_r);
-}
-
-struct filter *
-filter_configured_new(const struct config_param *param, GError **error_r)
-{
- const char *plugin_name;
- const struct filter_plugin *plugin;
-
- assert(param != NULL);
- assert(error_r == NULL || *error_r == NULL);
-
- plugin_name = config_get_block_string(param, "plugin", NULL);
- if (plugin_name == NULL) {
- g_set_error(error_r, config_quark(), 0,
- "No filter plugin specified");
- return NULL;
- }
-
- plugin = filter_plugin_by_name(plugin_name);
- if (plugin == NULL) {
- g_set_error(error_r, config_quark(), 0,
- "No such filter plugin: %s", plugin_name);
- return NULL;
- }
-
- return filter_new(plugin, param, error_r);
-}
-
-void
-filter_free(struct filter *filter)
-{
- assert(filter != NULL);
-
- filter->plugin->finish(filter);
-}
-
-const struct audio_format *
-filter_open(struct filter *filter, struct audio_format *audio_format,
- GError **error_r)
-{
- const struct audio_format *out_audio_format;
-
- assert(filter != NULL);
- assert(audio_format != NULL);
- assert(audio_format_valid(audio_format));
- assert(error_r == NULL || *error_r == NULL);
-
- out_audio_format = filter->plugin->open(filter, audio_format, error_r);
-
- assert(out_audio_format == NULL || audio_format_valid(audio_format));
- assert(out_audio_format == NULL ||
- audio_format_valid(out_audio_format));
-
- return out_audio_format;
-}
-
-void
-filter_close(struct filter *filter)
-{
- assert(filter != NULL);
-
- filter->plugin->close(filter);
-}
-
-const void *
-filter_filter(struct filter *filter, const void *src, size_t src_size,
- size_t *dest_size_r,
- GError **error_r)
-{
- assert(filter != NULL);
- assert(src != NULL);
- assert(src_size > 0);
- assert(dest_size_r != NULL);
- assert(error_r == NULL || *error_r == NULL);
-
- return filter->plugin->filter(filter, src, src_size, dest_size_r, error_r);
-}
diff --git a/src/filter_plugin.h b/src/filter_plugin.h
deleted file mode 100644
index 58e34dfb2..000000000
--- a/src/filter_plugin.h
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * 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.
- */
-
-/** \file
- *
- * This header declares the filter_plugin class. It describes a
- * plugin API for objects which filter raw PCM data.
- */
-
-#ifndef MPD_FILTER_PLUGIN_H
-#define MPD_FILTER_PLUGIN_H
-
-#include <glib.h>
-
-#include <stdbool.h>
-#include <stddef.h>
-
-struct config_param;
-struct filter;
-
-struct filter_plugin {
- const char *name;
-
- /**
- * Allocates and configures a filter.
- */
- struct filter *(*init)(const struct config_param *param,
- GError **error_r);
-
- /**
- * Free instance data.
- */
- void (*finish)(struct filter *filter);
-
- /**
- * Opens a filter.
- *
- * @param audio_format the audio format of incoming data; the
- * plugin may modify the object to enforce another input
- * format
- */
- const struct audio_format *
- (*open)(struct filter *filter,
- struct audio_format *audio_format,
- GError **error_r);
-
- /**
- * Closes a filter.
- */
- void (*close)(struct filter *filter);
-
- /**
- * Filters a block of PCM data.
- */
- const void *(*filter)(struct filter *filter,
- const void *src, size_t src_size,
- size_t *dest_buffer_r,
- GError **error_r);
-};
-
-/**
- * Creates a new instance of the specified filter plugin.
- *
- * @param plugin the filter plugin
- * @param param optional configuration section
- * @param error location to store the error occurring, or NULL to
- * ignore errors.
- * @return a new filter object, or NULL on error
- */
-struct filter *
-filter_new(const struct filter_plugin *plugin,
- const struct config_param *param, GError **error_r);
-
-/**
- * Creates a new filter, loads configuration and the plugin name from
- * the specified configuration section.
- *
- * @param param the configuration section
- * @param error location to store the error occurring, or NULL to
- * ignore errors.
- * @return a new filter object, or NULL on error
- */
-struct filter *
-filter_configured_new(const struct config_param *param, GError **error_r);
-
-/**
- * Deletes a filter. It must be closed prior to calling this
- * function, see filter_close().
- *
- * @param filter the filter object
- */
-void
-filter_free(struct filter *filter);
-
-/**
- * Opens the filter, preparing it for filter_filter().
- *
- * @param filter the filter object
- * @param audio_format the audio format of incoming data; the plugin
- * may modify the object to enforce another input format
- * @param error location to store the error occurring, or NULL to
- * ignore errors.
- * @return the format of outgoing data
- */
-const struct audio_format *
-filter_open(struct filter *filter, struct audio_format *audio_format,
- GError **error_r);
-
-/**
- * Closes the filter. After that, you may call filter_open() again.
- *
- * @param filter the filter object
- */
-void
-filter_close(struct filter *filter);
-
-/**
- * Filters a block of PCM data.
- *
- * @param filter the filter object
- * @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 NULL to
- * ignore errors.
- * @return the destination buffer on success (will be invalidated by
- * filter_close() or filter_filter()), NULL on error
- */
-const void *
-filter_filter(struct filter *filter, const void *src, size_t src_size,
- size_t *dest_size_r,
- GError **error_r);
-
-#endif
diff --git a/src/filter_registry.c b/src/filter_registry.c
deleted file mode 100644
index dc1889398..000000000
--- a/src/filter_registry.c
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "filter_registry.h"
-#include "filter_plugin.h"
-
-#include <stddef.h>
-#include <string.h>
-
-const struct filter_plugin *const filter_plugins[] = {
- &null_filter_plugin,
- &route_filter_plugin,
- &normalize_filter_plugin,
- &volume_filter_plugin,
- &replay_gain_filter_plugin,
- NULL,
-};
-
-const struct filter_plugin *
-filter_plugin_by_name(const char *name)
-{
- for (unsigned i = 0; filter_plugins[i] != NULL; ++i)
- if (strcmp(filter_plugins[i]->name, name) == 0)
- return filter_plugins[i];
-
- return NULL;
-}
diff --git a/src/filter_registry.h b/src/filter_registry.h
deleted file mode 100644
index d3949c7c4..000000000
--- a/src/filter_registry.h
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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.
- */
-
-/** \file
- *
- * This library manages all filter plugins which are enabled at
- * compile time.
- */
-
-#ifndef MPD_FILTER_REGISTRY_H
-#define MPD_FILTER_REGISTRY_H
-
-extern const struct filter_plugin null_filter_plugin;
-extern const struct filter_plugin chain_filter_plugin;
-extern const struct filter_plugin convert_filter_plugin;
-extern const struct filter_plugin route_filter_plugin;
-extern const struct filter_plugin normalize_filter_plugin;
-extern const struct filter_plugin volume_filter_plugin;
-extern const struct filter_plugin replay_gain_filter_plugin;
-
-const struct filter_plugin *
-filter_plugin_by_name(const char *name);
-
-#endif
diff --git a/src/fs/DirectoryReader.hxx b/src/fs/DirectoryReader.hxx
new file mode 100644
index 000000000..4e4a56345
--- /dev/null
+++ b/src/fs/DirectoryReader.hxx
@@ -0,0 +1,87 @@
+/*
+ * 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_DIRECTORY_READER_HXX
+#define MPD_FS_DIRECTORY_READER_HXX
+
+#include "check.h"
+#include "Path.hxx"
+
+#include <dirent.h>
+
+/**
+ * Reader for directory entries.
+ */
+class DirectoryReader {
+ DIR *const dirp;
+ dirent *ent;
+public:
+ /**
+ * Creates new directory reader for the specified #dir.
+ */
+ explicit DirectoryReader(const Path &dir)
+ : dirp(opendir(dir.c_str())),
+ ent(nullptr) {
+ }
+
+ DirectoryReader(const DirectoryReader &other) = delete;
+ DirectoryReader &operator=(const DirectoryReader &other) = delete;
+
+ /**
+ * Destroys this instance.
+ */
+ ~DirectoryReader() {
+ if (!HasFailed())
+ closedir(dirp);
+ }
+
+ /**
+ * Checks if directory failed to open.
+ */
+ bool HasFailed() const {
+ return dirp == nullptr;
+ }
+
+ /**
+ * Checks if directory entry is available.
+ */
+ bool HasEntry() const {
+ assert(!HasFailed());
+ return ent != nullptr;
+ }
+
+ /**
+ * Reads next directory entry.
+ */
+ bool ReadEntry() {
+ assert(!HasFailed());
+ ent = readdir(dirp);
+ return HasEntry();
+ }
+
+ /**
+ * Extracts directory entry that was previously read by #ReadEntry.
+ */
+ Path GetEntry() const {
+ assert(HasEntry());
+ return Path::FromFS(ent->d_name);
+ }
+};
+
+#endif
diff --git a/src/fs/FileSystem.cxx b/src/fs/FileSystem.cxx
new file mode 100644
index 000000000..70ab01fbd
--- /dev/null
+++ b/src/fs/FileSystem.cxx
@@ -0,0 +1,43 @@
+/*
+ * 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 "FileSystem.hxx"
+
+#include <errno.h>
+
+Path ReadLink(const Path &path)
+{
+#ifdef WIN32
+ (void)path;
+ errno = EINVAL;
+ return Path::Null();
+#else
+ char buffer[MPD_PATH_MAX];
+ ssize_t size = readlink(path.c_str(), buffer, MPD_PATH_MAX);
+ if (size < 0)
+ return Path::Null();
+ if (size >= MPD_PATH_MAX) {
+ errno = ENOMEM;
+ return Path::Null();
+ }
+ buffer[size] = '\0';
+ return Path::FromFS(buffer);
+#endif
+}
diff --git a/src/fs/FileSystem.hxx b/src/fs/FileSystem.hxx
new file mode 100644
index 000000000..2e1701c87
--- /dev/null
+++ b/src/fs/FileSystem.hxx
@@ -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.
+ */
+
+#ifndef MPD_FS_FILESYSTEM_HXX
+#define MPD_FS_FILESYSTEM_HXX
+
+#include "check.h"
+#include "fd_util.h"
+
+#include "Path.hxx"
+
+#include <sys/stat.h>
+#include <unistd.h>
+#include <assert.h>
+#include <stdio.h>
+
+namespace FOpenMode {
+/**
+ * Open mode for reading text files.
+ */
+constexpr Path::const_pointer ReadText = "r";
+
+/**
+ * Open mode for reading binary files.
+ */
+constexpr Path::const_pointer ReadBinary = "rb";
+
+/**
+ * Open mode for writing text files.
+ */
+constexpr Path::const_pointer WriteText = "w";
+
+/**
+ * Open mode for writing binary files.
+ */
+constexpr Path::const_pointer WriteBinary = "wb";
+
+/**
+ * Open mode for appending text files.
+ */
+constexpr Path::const_pointer AppendText = "a";
+
+/**
+ * Open mode for appending binary files.
+ */
+constexpr Path::const_pointer AppendBinary = "ab";
+}
+
+/**
+ * Wrapper for fopen() that uses #Path names.
+ */
+static inline FILE *FOpen(const Path &file, Path::const_pointer mode)
+{
+ return fopen(file.c_str(), mode);
+}
+
+/**
+ * Wrapper for open_cloexec() that uses #Path names.
+ */
+static inline int OpenFile(const Path &file, int flags, int mode)
+{
+ return open_cloexec(file.c_str(), flags, mode);
+}
+
+/**
+ * Wrapper for rename() that uses #Path names.
+ */
+static inline bool RenameFile(const Path &oldpath, const Path &newpath)
+{
+ return rename(oldpath.c_str(), newpath.c_str()) == 0;
+}
+
+/**
+ * Wrapper for stat() that uses #Path names.
+ */
+static inline bool StatFile(const Path &file, struct stat &buf,
+ bool follow_symlinks = true)
+{
+#ifdef WIN32
+ (void)follow_symlinks;
+ return stat(file.c_str(), &buf) == 0;
+#else
+ int ret = follow_symlinks
+ ? stat(file.c_str(), &buf)
+ : lstat(file.c_str(), &buf);
+ return ret == 0;
+#endif
+}
+
+/**
+ * Wrapper for unlink() that uses #Path names.
+ */
+static inline bool RemoveFile(const Path &file)
+{
+ return unlink(file.c_str()) == 0;
+}
+
+/**
+ * Wrapper for readlink() that uses #Path names.
+ */
+Path ReadLink(const Path &path);
+
+/**
+ * Wrapper for access() that uses #Path names.
+ */
+static inline bool CheckAccess(const Path &path, int mode)
+{
+#ifdef WIN32
+ (void)path;
+ (void)mode;
+ return true;
+#else
+ return access(path.c_str(), mode) == 0;
+#endif
+}
+
+/**
+ * Checks if #Path exists and is a regular file.
+ */
+static inline bool FileExists(const Path &path,
+ bool follow_symlinks = true)
+{
+ struct stat buf;
+ return StatFile(path, buf, follow_symlinks) && S_ISREG(buf.st_mode);
+}
+
+/**
+ * Checks if #Path exists and is a directory.
+ */
+static inline bool DirectoryExists(const Path &path,
+ bool follow_symlinks = true)
+{
+ struct stat buf;
+ return StatFile(path, buf, follow_symlinks) && S_ISDIR(buf.st_mode);
+}
+
+/**
+ * Checks if #Path exists.
+ */
+static inline bool PathExists(const Path &path,
+ bool follow_symlinks = true)
+{
+ struct stat buf;
+ return StatFile(path, buf, follow_symlinks);
+}
+
+#endif
diff --git a/src/fs/Path.cxx b/src/fs/Path.cxx
new file mode 100644
index 000000000..cb808b36c
--- /dev/null
+++ b/src/fs/Path.cxx
@@ -0,0 +1,150 @@
+/*
+ * 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 "fs/Path.hxx"
+#include "conf.h"
+#include "mpd_error.h"
+#include "gcc.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+
+#ifdef G_OS_WIN32
+#include <windows.h> // for GetACP()
+#include <stdio.h> // for sprintf()
+#endif
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "path"
+
+/**
+ * Maximal number of bytes required to represent path name in UTF-8
+ * (including nul-terminator).
+ * This value is a rought estimate of upper bound.
+ * It's based on path name limit in bytes (MPD_PATH_MAX)
+ * and assumption that some weird encoding could represent some UTF-8 4 byte
+ * sequences with single byte.
+ */
+#define MPD_PATH_MAX_UTF8 ((MPD_PATH_MAX - 1) * 4 + 1)
+
+std::string fs_charset;
+
+std::string Path::ToUTF8(const_pointer path_fs)
+{
+ if (path_fs == nullptr)
+ return std::string();
+
+ GIConv conv = g_iconv_open("utf-8", fs_charset.c_str());
+ if (conv == reinterpret_cast<GIConv>(-1))
+ return std::string();
+
+ // g_iconv() does not need nul-terminator,
+ // std::string could be created without it too.
+ char path_utf8[MPD_PATH_MAX_UTF8 - 1];
+ char *in = const_cast<char *>(path_fs);
+ char *out = path_utf8;
+ size_t in_left = strlen(path_fs);
+ size_t out_left = sizeof(path_utf8);
+
+ size_t ret = g_iconv(conv, &in, &in_left, &out, &out_left);
+
+ g_iconv_close(conv);
+
+ if (ret == static_cast<size_t>(-1) || in_left > 0)
+ return std::string();
+
+ return std::string(path_utf8, sizeof(path_utf8) - out_left);
+}
+
+Path Path::FromUTF8(const char *path_utf8)
+{
+ gchar *p;
+
+ p = g_convert(path_utf8, -1,
+ fs_charset.c_str(), "utf-8",
+ NULL, NULL, NULL);
+
+ return Path(Donate(), p);
+}
+
+gcc_pure
+static bool
+IsSupportedCharset(const char *charset)
+{
+ /* convert a space to check if the charset is valid */
+ char *test = g_convert(" ", 1, charset, "UTF-8", NULL, NULL, NULL);
+ if (test == NULL)
+ return false;
+
+ g_free(test);
+ return true;
+}
+
+static void
+SetFSCharset(const char *charset)
+{
+ assert(charset != NULL);
+
+ if (!IsSupportedCharset(charset))
+ MPD_ERROR("invalid filesystem charset: %s", charset);
+
+ fs_charset = charset;
+
+ g_debug("SetFSCharset: fs charset is: %s", fs_charset.c_str());
+}
+
+const std::string &Path::GetFSCharset()
+{
+ return fs_charset;
+}
+
+void Path::GlobalInit()
+{
+ const char *charset = NULL;
+
+ charset = config_get_string(CONF_FS_CHARSET, NULL);
+ if (charset == NULL) {
+#ifndef G_OS_WIN32
+ const gchar **encodings;
+ g_get_filename_charsets(&encodings);
+
+ if (encodings[0] != NULL && *encodings[0] != '\0')
+ charset = encodings[0];
+#else /* G_OS_WIN32 */
+ /* Glib claims that file system encoding is always utf-8
+ * on native Win32 (i.e. not Cygwin).
+ * However this is true only if <gstdio.h> helpers are used.
+ * MPD uses regular <stdio.h> functions.
+ * Those functions use encoding determined by GetACP(). */
+ static char win_charset[13];
+ sprintf(win_charset, "cp%u", GetACP());
+ charset = win_charset;
+#endif
+ }
+
+ if (charset) {
+ SetFSCharset(charset);
+ } else {
+ g_message("setting filesystem charset to ISO-8859-1");
+ SetFSCharset("ISO-8859-1");
+ }
+}
diff --git a/src/fs/Path.hxx b/src/fs/Path.hxx
new file mode 100644
index 000000000..eaab2bde5
--- /dev/null
+++ b/src/fs/Path.hxx
@@ -0,0 +1,267 @@
+/*
+ * 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_PATH_HXX
+#define MPD_FS_PATH_HXX
+
+#include "check.h"
+#include "gcc.h"
+
+#include <glib.h>
+
+#include <algorithm>
+#include <string>
+
+#include <assert.h>
+#include <string.h>
+#include <limits.h>
+
+#if !defined(MPD_PATH_MAX)
+# if defined(WIN32)
+# define MPD_PATH_MAX 260
+# elif defined(MAXPATHLEN)
+# define MPD_PATH_MAX MAXPATHLEN
+# elif defined(PATH_MAX)
+# define MPD_PATH_MAX PATH_MAX
+# else
+# define MPD_PATH_MAX 256
+# endif
+#endif
+
+/**
+ * A path name in the native file system character set.
+ */
+class Path {
+public:
+ typedef char value_type;
+ typedef value_type *pointer;
+ typedef const value_type *const_pointer;
+
+private:
+ pointer value;
+
+ struct Donate {};
+
+ /**
+ * Donate the allocated pointer to a new #Path object.
+ */
+ constexpr Path(Donate, pointer _value):value(_value) {}
+
+ /**
+ * Release memory allocated by the value, but do not clear the
+ * value pointer.
+ */
+ void Free() {
+ /* free() can be optimized by gcc, while g_free() can
+ not: when the compiler knows that the value is
+ nullptr, it will not emit a free() call in the
+ inlined destructor; however on Windows, we need to
+ call g_free(), because the value has been allocated
+ by GLib, and on Windows, this matters */
+#ifdef WIN32
+ g_free(value);
+#else
+ free(value);
+#endif
+ }
+
+public:
+ /**
+ * Copy a #Path object.
+ */
+ Path(const Path &other)
+ :value(g_strdup(other.value)) {}
+
+ /**
+ * Move a #Path object.
+ */
+ Path(Path &&other):value(other.value) {
+ other.value = nullptr;
+ }
+
+ ~Path() {
+ Free();
+ }
+
+ /**
+ * Return a "nulled" instance. Its IsNull() method will
+ * return true. Such an object must not be used.
+ *
+ * @see IsNull()
+ */
+ gcc_const
+ static Path Null() {
+ return Path(Donate(), nullptr);
+ }
+
+ /**
+ * Join two path components with the path separator.
+ */
+ gcc_pure gcc_nonnull_all
+ static Path Build(const_pointer a, const_pointer b) {
+ return Path(Donate(), g_build_filename(a, b, nullptr));
+ }
+
+ gcc_pure gcc_nonnull_all
+ static Path Build(const_pointer a, const Path &b) {
+ return Build(a, b.c_str());
+ }
+
+ gcc_pure gcc_nonnull_all
+ static Path Build(const Path &a, const_pointer b) {
+ return Build(a.c_str(), b);
+ }
+
+ gcc_pure
+ static Path Build(const Path &a, const Path &b) {
+ return Build(a.c_str(), b.c_str());
+ }
+
+ /**
+ * Convert a C string that is already in the filesystem
+ * character set to a #Path instance.
+ */
+ gcc_pure
+ static Path FromFS(const_pointer fs) {
+ return Path(Donate(), g_strdup(fs));
+ }
+
+ /**
+ * Convert a UTF-8 C string to a #Path instance.
+ * Returns return a "nulled" instance on error.
+ */
+ gcc_pure
+ static Path FromUTF8(const char *path_utf8);
+
+ /**
+ * Convert the path to UTF-8.
+ * Returns empty string on error or if #path_fs is null pointer.
+ */
+ gcc_pure
+ static std::string ToUTF8(const_pointer path_fs);
+
+ /**
+ * Performs global one-time initialization of this class.
+ */
+ static void GlobalInit();
+
+ /**
+ * Gets file system character set name.
+ */
+ static const std::string &GetFSCharset();
+
+ /**
+ * Copy a #Path object.
+ */
+ Path &operator=(const Path &other) {
+ if (this != &other) {
+ Free();
+ value = g_strdup(other.value);
+ }
+
+ return *this;
+ }
+
+ /**
+ * Move a #Path object.
+ */
+ Path &operator=(Path &&other) {
+ std::swap(value, other.value);
+ return *this;
+ }
+
+ /**
+ * Steal the allocated value. This object has an undefined
+ * value, and the caller is response for freeing this method's
+ * return value.
+ */
+ pointer Steal() {
+ pointer result = value;
+ value = nullptr;
+ return result;
+ }
+
+ /**
+ * Check if this is a "nulled" instance. A "nulled" instance
+ * must not be used.
+ */
+ bool IsNull() const {
+ return value == nullptr;
+ }
+
+ /**
+ * Clear this object's value, make it "nulled".
+ *
+ * @see IsNull()
+ */
+ void SetNull() {
+ Free();
+ value = nullptr;
+ }
+
+ gcc_pure
+ bool empty() const {
+ assert(value != nullptr);
+
+ return *value == 0;
+ }
+
+ /**
+ * @return the length of this string in number of "value_type"
+ * elements (which may not be the number of characters).
+ */
+ gcc_pure
+ size_t length() const {
+ assert(value != nullptr);
+
+ return strlen(value);
+ }
+
+ /**
+ * Returns the value as a const C string. The returned
+ * pointer is invalidated whenever the value of life of this
+ * instance ends.
+ */
+ gcc_pure
+ const_pointer c_str() const {
+ assert(value != nullptr);
+
+ return value;
+ }
+
+ /**
+ * Convert the path to UTF-8.
+ * Returns empty string on error or if this instance is "nulled"
+ * (#IsNull returns true).
+ */
+ std::string ToUTF8() const {
+ return ToUTF8(value);
+ }
+
+ /**
+ * Gets directory name of this path.
+ * Returns a "nulled" instance on error.
+ */
+ Path GetDirectoryName() const {
+ assert(value != nullptr);
+ return Path(Donate(), g_path_get_dirname(value));
+ }
+};
+
+#endif
diff --git a/src/gcc.h b/src/gcc.h
index 45f7101f3..1bd770597 100644
--- a/src/gcc.h
+++ b/src/gcc.h
@@ -32,6 +32,9 @@
*/
#if GCC_CHECK_VERSION(3,0)
+# define gcc_const __attribute__((const))
+# define gcc_pure __attribute__((pure))
+# define gcc_malloc __attribute__((malloc))
# define gcc_must_check __attribute__ ((warn_unused_result))
# define gcc_packed __attribute__ ((packed))
/* these are very useful for type checking */
@@ -41,11 +44,21 @@
# define gcc_fprintf__ __attribute__ ((format(printf,4,5)))
# define gcc_scanf __attribute__ ((format(scanf,1,2)))
# define gcc_used __attribute__ ((used))
+# define gcc_unused __attribute__((unused))
+# define gcc_warn_unused_result __attribute__((warn_unused_result))
/* # define inline inline __attribute__ ((always_inline)) */
# define gcc_noinline __attribute__ ((noinline))
# define gcc_nonnull(...) __attribute__((nonnull(__VA_ARGS__)))
# define gcc_nonnull_all __attribute__((nonnull))
+
+# define gcc_likely(x) __builtin_expect (!!(x), 1)
+# define gcc_unlikely(x) __builtin_expect (!!(x), 0)
+
#else
+# define gcc_unused
+# define gcc_const
+# define gcc_pure
+# define gcc_malloc
# define gcc_must_check
# define gcc_packed
# define gcc_printf
@@ -54,10 +67,44 @@
# define gcc_fprintf__
# define gcc_scanf
# define gcc_used
+# define gcc_unused
+# define gcc_warn_unused_result
/* # define inline */
# define gcc_noinline
# define gcc_nonnull(...)
# define gcc_nonnull_all
+
+# define gcc_likely(x) (x)
+# define gcc_unlikely(x) (x)
+
+#endif
+
+#if defined(__GNUC__) || defined(__clang__)
+#define gcc_unreachable() __builtin_unreachable()
+#else
+#define gcc_unreachable()
+#endif
+
+#ifdef __cplusplus
+
+#ifdef __GNUC__
+/* "__restrict__" is a GCC extension for C++ */
+#define restrict __restrict__
+#else
+/* disable it on other compilers */
+#define restrict
+#endif
+
+#if !defined(__clang__) && defined(__GNUC__) && !GCC_CHECK_VERSION(4,6)
+#error Your gcc version is too old. MPD requires gcc 4.6 or newer.
+#endif
+
+/* support for C++11 "override" was added in gcc 4.7 */
+#if !defined(__clang__) && defined(__GNUC__) && !GCC_CHECK_VERSION(4,7)
+#define override
+#define final
+#endif
+
#endif
#endif /* MPD_GCC_H */
diff --git a/src/gerror.h b/src/gerror.h
new file mode 100644
index 000000000..fe4c54da9
--- /dev/null
+++ b/src/gerror.h
@@ -0,0 +1,25 @@
+/*
+ * 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_GERROR_H
+#define MPD_GERROR_H
+
+typedef struct _GError GError;
+
+#endif
diff --git a/src/glib_compat.h b/src/glib_compat.h
index 97d1fdc0c..a16b9c6eb 100644
--- a/src/glib_compat.h
+++ b/src/glib_compat.h
@@ -28,17 +28,6 @@
#include <glib.h>
-#if !GLIB_CHECK_VERSION(2,18,0)
-
-static inline void
-g_set_error_literal(GError **err, GQuark domain, gint code,
- const gchar *message)
-{
- g_set_error(err, domain, code, "%s", message);
-}
-
-#endif
-
#if !GLIB_CHECK_VERSION(2,28,0)
static inline gint64
diff --git a/src/glib_socket.h b/src/glib_socket.h
deleted file mode 100644
index 46fab37dd..000000000
--- a/src/glib_socket.h
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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_GLIB_SOCKET_H
-#define MPD_GLIB_SOCKET_H
-
-#include <glib.h>
-
-/**
- * Portable wrapper for g_io_channel_unix_new() or
- * g_io_channel_win32_new_socket().
- */
-G_GNUC_MALLOC
-static inline GIOChannel *
-g_io_channel_new_socket(int fd)
-{
-#ifdef G_OS_WIN32
- return g_io_channel_win32_new_socket(fd);
-#else
- return g_io_channel_unix_new(fd);
-#endif
-}
-
-#endif
diff --git a/src/icy_metadata.c b/src/icy_metadata.c
deleted file mode 100644
index 32953e69f..000000000
--- a/src/icy_metadata.c
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "icy_metadata.h"
-#include "tag.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "icy_metadata"
-
-void
-icy_deinit(struct icy_metadata *im)
-{
- if (!icy_defined(im))
- return;
-
- if (im->data_rest == 0 && im->meta_size > 0)
- g_free(im->meta_data);
-
- if (im->tag != NULL)
- tag_free(im->tag);
-}
-
-void
-icy_reset(struct icy_metadata *im)
-{
- if (!icy_defined(im))
- return;
-
- icy_deinit(im);
-
- im->data_rest = im->data_size;
- im->meta_size = 0;
-}
-
-size_t
-icy_data(struct icy_metadata *im, size_t length)
-{
- assert(length > 0);
-
- if (!icy_defined(im))
- return length;
-
- if (im->data_rest == 0)
- return 0;
-
- if (length >= im->data_rest) {
- length = im->data_rest;
- im->data_rest = 0;
- } else
- im->data_rest -= length;
-
- return length;
-}
-
-static void
-icy_add_item(struct tag *tag, enum tag_type type, const char *value)
-{
- size_t length = strlen(value);
-
- if (length >= 2 && value[0] == '\'' && value[length - 1] == '\'') {
- /* strip the single quotes */
- ++value;
- length -= 2;
- }
-
- if (length > 0)
- tag_add_item_n(tag, type, value, length);
-}
-
-static void
-icy_parse_tag_item(struct tag *tag, const char *item)
-{
- gchar **p = g_strsplit(item, "=", 0);
-
- if (p[0] != NULL && p[1] != NULL) {
- if (strcmp(p[0], "StreamTitle") == 0)
- icy_add_item(tag, TAG_TITLE, p[1]);
- else
- g_debug("unknown icy-tag: '%s'", p[0]);
- }
-
- g_strfreev(p);
-}
-
-static struct tag *
-icy_parse_tag(const char *p)
-{
- struct tag *tag = tag_new();
- gchar **items = g_strsplit(p, ";", 0);
-
- for (unsigned i = 0; items[i] != NULL; ++i)
- icy_parse_tag_item(tag, items[i]);
-
- g_strfreev(items);
-
- return tag;
-}
-
-size_t
-icy_meta(struct icy_metadata *im, const void *data, size_t length)
-{
- const unsigned char *p = data;
-
- assert(icy_defined(im));
- assert(im->data_rest == 0);
- assert(length > 0);
-
- if (im->meta_size == 0) {
- /* read meta_size from the first byte of a meta
- block */
- im->meta_size = *p++ * 16;
- if (im->meta_size == 0) {
- /* special case: no metadata */
- im->data_rest = im->data_size;
- return 1;
- }
-
- /* 1 byte was consumed (must be re-added later for the
- return value */
- --length;
-
- /* initialize metadata reader, allocate enough
- memory (+1 for the null terminator) */
- im->meta_position = 0;
- im->meta_data = g_malloc(im->meta_size + 1);
- }
-
- assert(im->meta_position < im->meta_size);
-
- if (length > im->meta_size - im->meta_position)
- length = im->meta_size - im->meta_position;
-
- memcpy(im->meta_data + im->meta_position, p, length);
- im->meta_position += length;
-
- if (p != data)
- /* re-add the first byte (which contained meta_size) */
- ++length;
-
- if (im->meta_position == im->meta_size) {
- /* null-terminate the string */
-
- im->meta_data[im->meta_size] = 0;
-
- /* parse */
-
- if (im->tag != NULL)
- tag_free(im->tag);
-
- im->tag = icy_parse_tag(im->meta_data);
- g_free(im->meta_data);
-
- /* change back to normal data mode */
-
- im->meta_size = 0;
- im->data_rest = im->data_size;
- }
-
- return length;
-}
diff --git a/src/icy_metadata.h b/src/icy_metadata.h
deleted file mode 100644
index 9797122ca..000000000
--- a/src/icy_metadata.h
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * 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 ICY_METADATA_H
-#define ICY_METADATA_H
-
-#include <stdbool.h>
-#include <stddef.h>
-
-struct icy_metadata {
- size_t data_size, data_rest;
-
- size_t meta_size, meta_position;
- char *meta_data;
-
- struct tag *tag;
-};
-
-/**
- * Initialize a disabled icy_metadata object.
- */
-static inline void
-icy_clear(struct icy_metadata *im)
-{
- im->data_size = 0;
-}
-
-/**
- * Initialize an enabled icy_metadata object with the specified
- * data_size (from the icy-metaint HTTP response header).
- */
-static inline void
-icy_start(struct icy_metadata *im, size_t data_size)
-{
- im->data_size = im->data_rest = data_size;
- im->meta_size = 0;
- im->tag = NULL;
-}
-
-/**
- * Resets the icy_metadata. Call this after rewinding the stream.
- */
-void
-icy_reset(struct icy_metadata *im);
-
-void
-icy_deinit(struct icy_metadata *im);
-
-/**
- * Checks whether the icy_metadata object is enabled.
- */
-static inline bool
-icy_defined(const struct icy_metadata *im)
-{
- return im->data_size > 0;
-}
-
-/**
- * Evaluates data. Returns the number of bytes of normal data which
- * can be read by the caller, but not more than "length". If the
- * return value is smaller than "length", the caller should invoke
- * icy_meta().
- */
-size_t
-icy_data(struct icy_metadata *im, size_t length);
-
-/**
- * Reads metadata from the stream. Returns the number of bytes
- * consumed. If the return value is smaller than "length", the caller
- * should invoke icy_data().
- */
-size_t
-icy_meta(struct icy_metadata *im, const void *data, size_t length);
-
-static inline struct tag *
-icy_tag(struct icy_metadata *im)
-{
- struct tag *tag = im->tag;
- im->tag = NULL;
- return tag;
-}
-
-#endif
diff --git a/src/icy_server.c b/src/icy_server.c
deleted file mode 100644
index b6c89eaf6..000000000
--- a/src/icy_server.c
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "icy_server.h"
-
-#include <glib.h>
-
-#include <assert.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "icy_server"
-
-char*
-icy_server_metadata_header(const char *name,
- const char *genre, const char *url,
- const char *content_type, int metaint)
-{
- return g_strdup_printf("ICY 200 OK\r\n"
- "icy-notice1:<BR>This stream requires an audio player!<BR>\r\n" /* TODO */
- "icy-notice2:MPD - The music player daemon<BR>\r\n"
- "icy-name: %s\r\n" /* TODO */
- "icy-genre: %s\r\n" /* TODO */
- "icy-url: %s\r\n" /* TODO */
- "icy-pub:1\r\n"
- "icy-metaint:%d\r\n"
- /* TODO "icy-br:%d\r\n" */
- "Content-Type: %s\r\n"
- "Connection: close\r\n"
- "Pragma: no-cache\r\n"
- "Cache-Control: no-cache, no-store\r\n"
- "\r\n",
- name,
- genre,
- url,
- metaint,
- /* bitrate, */
- content_type);
-}
-
-static char *
-icy_server_metadata_string(const char *stream_title, const char* stream_url)
-{
- gchar *icy_metadata;
- guint meta_length;
-
- // The leading n is a placeholder for the length information
- icy_metadata = g_strdup_printf("nStreamTitle='%s';"
- "StreamUrl='%s';",
- stream_title,
- stream_url);
-
- g_return_val_if_fail(icy_metadata, NULL);
-
- meta_length = strlen(icy_metadata);
-
- meta_length--; // subtract placeholder
-
- meta_length = ((int)meta_length / 16) + 1;
-
- icy_metadata[0] = meta_length;
-
- if (meta_length > 255) {
- g_free(icy_metadata);
- return NULL;
- }
-
- return icy_metadata;
-}
-
-struct page*
-icy_server_metadata_page(const struct tag *tag, ...)
-{
- va_list args;
- const gchar *tag_items[TAG_NUM_OF_ITEM_TYPES];
- gint last_item, item;
- guint position;
- gchar *icy_string;
- struct page *icy_metadata;
- gchar stream_title[(1 + 255 - 28) * 16]; // Length + Metadata -
- // "StreamTitle='';StreamUrl='';"
- // = 4081 - 28
- stream_title[0] = '\0';
-
- last_item = -1;
-
- va_start(args, tag);
- while (1) {
- enum tag_type type;
- const gchar *tag_item;
-
- type = va_arg(args, enum tag_type);
-
- if (type == TAG_NUM_OF_ITEM_TYPES)
- break;
-
- tag_item = tag_get_value(tag, type);
-
- if (tag_item)
- tag_items[++last_item] = tag_item;
- }
- va_end(args);
-
- position = item = 0;
- while (position < sizeof(stream_title) && item <= last_item) {
- gint length = 0;
-
- length = g_strlcpy(stream_title + position,
- tag_items[item++],
- sizeof(stream_title) - position);
-
- position += length;
-
- if (item <= last_item) {
- length = g_strlcpy(stream_title + position,
- " - ",
- sizeof(stream_title) - position);
-
- position += length;
- }
- }
-
- icy_string = icy_server_metadata_string(stream_title, "");
-
- if (icy_string == NULL)
- return NULL;
-
- icy_metadata = page_new_copy(icy_string, (icy_string[0] * 16) + 1);
-
- g_free(icy_string);
-
- return icy_metadata;
-}
diff --git a/src/icy_server.h b/src/icy_server.h
deleted file mode 100644
index 04f21d2ad..000000000
--- a/src/icy_server.h
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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 ICY_SERVER_H
-#define ICY_SERVER_H
-
-#include "page.h"
-#include "tag.h"
-
-#include <stdarg.h>
-
-char*
-icy_server_metadata_header(const char *name,
- const char *genre, const char *url,
- const char *content_type, int metaint);
-
-struct page*
-icy_server_metadata_page(const struct tag *tag, ...);
-
-#endif
diff --git a/src/idle.c b/src/idle.c
deleted file mode 100644
index 2d174d78a..000000000
--- a/src/idle.c
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * Support library for the "idle" command.
- *
- */
-
-#include "config.h"
-#include "idle.h"
-#include "event_pipe.h"
-
-#include <assert.h>
-#include <glib.h>
-
-static unsigned idle_flags;
-static GMutex *idle_mutex = NULL;
-
-static const char *const idle_names[] = {
- "database",
- "stored_playlist",
- "playlist",
- "player",
- "mixer",
- "output",
- "options",
- "sticker",
- "update",
- "subscription",
- "message",
- NULL
-};
-
-void
-idle_init(void)
-{
- g_assert(idle_mutex == NULL);
- idle_mutex = g_mutex_new();
-}
-
-void
-idle_deinit(void)
-{
- g_assert(idle_mutex != NULL);
- g_mutex_free(idle_mutex);
- idle_mutex = NULL;
-}
-
-void
-idle_add(unsigned flags)
-{
- assert(flags != 0);
-
- g_mutex_lock(idle_mutex);
- idle_flags |= flags;
- g_mutex_unlock(idle_mutex);
-
- event_pipe_emit(PIPE_EVENT_IDLE);
-}
-
-unsigned
-idle_get(void)
-{
- unsigned flags;
-
- g_mutex_lock(idle_mutex);
- flags = idle_flags;
- idle_flags = 0;
- g_mutex_unlock(idle_mutex);
-
- return flags;
-}
-
-const char*const*
-idle_get_names(void)
-{
- return idle_names;
-}
diff --git a/src/idle.h b/src/idle.h
deleted file mode 100644
index 0156933c0..000000000
--- a/src/idle.h
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * Support library for the "idle" command.
- *
- */
-
-#ifndef MPD_IDLE_H
-#define MPD_IDLE_H
-
-enum {
- /** song database has been updated*/
- IDLE_DATABASE = 0x1,
-
- /** a stored playlist has been modified, created, deleted or
- renamed */
- IDLE_STORED_PLAYLIST = 0x2,
-
- /** the current playlist has been modified */
- IDLE_PLAYLIST = 0x4,
-
- /** the player state has changed: play, stop, pause, seek, ... */
- IDLE_PLAYER = 0x8,
-
- /** the volume has been modified */
- IDLE_MIXER = 0x10,
-
- /** an audio output device has been enabled or disabled */
- IDLE_OUTPUT = 0x20,
-
- /** options have changed: crossfade, random, repeat, ... */
- IDLE_OPTIONS = 0x40,
-
- /** a sticker has been modified. */
- IDLE_STICKER = 0x80,
-
- /** a database update has started or finished. */
- IDLE_UPDATE = 0x100,
-
- /** a client has subscribed or unsubscribed to/from a channel */
- IDLE_SUBSCRIPTION = 0x200,
-
- /** a message on the subscribed channel was receivedd */
- IDLE_MESSAGE = 0x400,
-};
-
-/**
- * Initialize the mutex
- */
-void
-idle_init(void);
-
-/**
- * Destroy the mutex
- */
-void
-idle_deinit(void);
-
-/**
- * Adds idle flag (with bitwise "or") and queues notifications to all
- * clients.
- */
-void
-idle_add(unsigned flags);
-
-/**
- * Atomically reads and resets the global idle flags value.
- */
-unsigned
-idle_get(void);
-
-/**
- * Get idle names
- */
-const char*const*
-idle_get_names(void);
-
-#endif
diff --git a/src/inotify_queue.c b/src/inotify_queue.c
deleted file mode 100644
index d5e2228c3..000000000
--- a/src/inotify_queue.c
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "inotify_queue.h"
-#include "update.h"
-
-#include <glib.h>
-
-#include <string.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "inotify"
-
-enum {
- /**
- * Wait this long after the last change before calling
- * update_enqueue(). This increases the probability that
- * updates can be bundled.
- */
- INOTIFY_UPDATE_DELAY_S = 5,
-};
-
-static GSList *inotify_queue;
-static guint queue_source_id;
-
-void
-mpd_inotify_queue_init(void)
-{
-}
-
-static void
-free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
-{
- g_free(data);
-}
-
-void
-mpd_inotify_queue_finish(void)
-{
- if (queue_source_id != 0)
- g_source_remove(queue_source_id);
-
- g_slist_foreach(inotify_queue, free_callback, NULL);
- g_slist_free(inotify_queue);
-}
-
-static gboolean
-mpd_inotify_run_update(G_GNUC_UNUSED gpointer data)
-{
- unsigned id;
-
- while (inotify_queue != NULL) {
- char *uri_utf8 = inotify_queue->data;
-
- id = update_enqueue(uri_utf8, false);
- if (id == 0)
- /* retry later */
- return true;
-
- g_debug("updating '%s' job=%u", uri_utf8, id);
-
- g_free(uri_utf8);
- inotify_queue = g_slist_delete_link(inotify_queue,
- inotify_queue);
- }
-
- /* done, remove the timer event by returning false */
- queue_source_id = 0;
- return false;
-}
-
-static bool
-path_in(const char *path, const char *possible_parent)
-{
- size_t length = strlen(possible_parent);
-
- return path[0] == 0 ||
- (memcmp(possible_parent, path, length) == 0 &&
- (path[length] == 0 || path[length] == '/'));
-}
-
-void
-mpd_inotify_enqueue(char *uri_utf8)
-{
- GSList *old_queue = inotify_queue;
-
- if (queue_source_id != 0)
- g_source_remove(queue_source_id);
- queue_source_id = g_timeout_add_seconds(INOTIFY_UPDATE_DELAY_S,
- mpd_inotify_run_update, NULL);
-
- inotify_queue = NULL;
- while (old_queue != NULL) {
- char *current_uri = old_queue->data;
-
- if (path_in(uri_utf8, current_uri)) {
- /* already enqueued */
- g_free(uri_utf8);
- inotify_queue = g_slist_concat(inotify_queue,
- old_queue);
- return;
- }
-
- old_queue = g_slist_delete_link(old_queue, old_queue);
-
- if (path_in(current_uri, uri_utf8))
- /* existing path is a sub-path of the new
- path; we can dequeue the existing path and
- update the new path instead */
- g_free(current_uri);
- else
- /* move the existing path to the new queue */
- inotify_queue = g_slist_prepend(inotify_queue,
- current_uri);
- }
-
- inotify_queue = g_slist_prepend(inotify_queue, uri_utf8);
-}
diff --git a/src/inotify_queue.h b/src/inotify_queue.h
deleted file mode 100644
index cfc28ebfe..000000000
--- a/src/inotify_queue.h
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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_INOTIFY_QUEUE_H
-#define MPD_INOTIFY_QUEUE_H
-
-void
-mpd_inotify_queue_init(void);
-
-void
-mpd_inotify_queue_finish(void);
-
-void
-mpd_inotify_enqueue(char *uri_utf8);
-
-#endif
diff --git a/src/inotify_source.c b/src/inotify_source.c
deleted file mode 100644
index e415f5e72..000000000
--- a/src/inotify_source.c
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "inotify_source.h"
-#include "fifo_buffer.h"
-#include "fd_util.h"
-#include "mpd_error.h"
-
-#include <sys/inotify.h>
-#include <unistd.h>
-#include <errno.h>
-#include <stdbool.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "inotify"
-
-struct mpd_inotify_source {
- int fd;
-
- GIOChannel *channel;
-
- /**
- * The channel's source id in the GLib main loop.
- */
- guint id;
-
- struct fifo_buffer *buffer;
-
- mpd_inotify_callback_t callback;
- void *callback_ctx;
-};
-
-/**
- * A GQuark for GError instances.
- */
-static inline GQuark
-mpd_inotify_quark(void)
-{
- return g_quark_from_static_string("inotify");
-}
-
-static gboolean
-mpd_inotify_in_event(G_GNUC_UNUSED GIOChannel *_source,
- G_GNUC_UNUSED GIOCondition condition,
- gpointer data)
-{
- struct mpd_inotify_source *source = data;
- void *dest;
- size_t length;
- ssize_t nbytes;
- const struct inotify_event *event;
-
- dest = fifo_buffer_write(source->buffer, &length);
- if (dest == NULL)
- MPD_ERROR("buffer full");
-
- nbytes = read(source->fd, dest, length);
- if (nbytes < 0)
- MPD_ERROR("failed to read from inotify: %s",
- g_strerror(errno));
- if (nbytes == 0)
- MPD_ERROR("end of file from inotify");
-
- fifo_buffer_append(source->buffer, nbytes);
-
- while (true) {
- const char *name;
-
- event = fifo_buffer_read(source->buffer, &length);
- if (event == NULL || length < sizeof(*event) ||
- length < sizeof(*event) + event->len)
- break;
-
- if (event->len > 0 && event->name[event->len - 1] == 0)
- name = event->name;
- else
- name = NULL;
-
- source->callback(event->wd, event->mask, name,
- source->callback_ctx);
- fifo_buffer_consume(source->buffer,
- sizeof(*event) + event->len);
- }
-
- return true;
-}
-
-struct mpd_inotify_source *
-mpd_inotify_source_new(mpd_inotify_callback_t callback, void *callback_ctx,
- GError **error_r)
-{
- struct mpd_inotify_source *source =
- g_new(struct mpd_inotify_source, 1);
-
- source->fd = inotify_init_cloexec();
- if (source->fd < 0) {
- g_set_error(error_r, mpd_inotify_quark(), errno,
- "inotify_init() has failed: %s",
- g_strerror(errno));
- g_free(source);
- return NULL;
- }
-
- source->buffer = fifo_buffer_new(4096);
-
- source->channel = g_io_channel_unix_new(source->fd);
- source->id = g_io_add_watch(source->channel, G_IO_IN,
- mpd_inotify_in_event, source);
-
- source->callback = callback;
- source->callback_ctx = callback_ctx;
-
- return source;
-}
-
-void
-mpd_inotify_source_free(struct mpd_inotify_source *source)
-{
- g_source_remove(source->id);
- g_io_channel_unref(source->channel);
- fifo_buffer_free(source->buffer);
- close(source->fd);
- g_free(source);
-}
-
-int
-mpd_inotify_source_add(struct mpd_inotify_source *source,
- const char *path_fs, unsigned mask,
- GError **error_r)
-{
- int wd = inotify_add_watch(source->fd, path_fs, mask);
- if (wd < 0)
- g_set_error(error_r, mpd_inotify_quark(), errno,
- "inotify_add_watch() has failed: %s",
- g_strerror(errno));
-
- return wd;
-}
-
-void
-mpd_inotify_source_rm(struct mpd_inotify_source *source, unsigned wd)
-{
- int ret = inotify_rm_watch(source->fd, wd);
- if (ret < 0 && errno != EINVAL)
- g_warning("inotify_rm_watch() has failed: %s",
- g_strerror(errno));
-
- /* EINVAL may happen here when the file has been deleted; the
- kernel seems to auto-unregister deleted files */
-}
diff --git a/src/inotify_source.h b/src/inotify_source.h
deleted file mode 100644
index f92e18e39..000000000
--- a/src/inotify_source.h
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * 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_INOTIFY_SOURCE_H
-#define MPD_INOTIFY_SOURCE_H
-
-#include <glib.h>
-
-typedef void (*mpd_inotify_callback_t)(int wd, unsigned mask,
- const char *name, void *ctx);
-
-struct mpd_inotify_source;
-
-/**
- * Creates a new inotify source and registers it in the GLib main
- * loop.
- *
- * @param a callback invoked for events received from the kernel
- */
-struct mpd_inotify_source *
-mpd_inotify_source_new(mpd_inotify_callback_t callback, void *callback_ctx,
- GError **error_r);
-
-void
-mpd_inotify_source_free(struct mpd_inotify_source *source);
-
-/**
- * Adds a path to the notify list.
- *
- * @return a watch descriptor or -1 on error
- */
-int
-mpd_inotify_source_add(struct mpd_inotify_source *source,
- const char *path_fs, unsigned mask,
- GError **error_r);
-
-/**
- * Removes a path from the notify list.
- *
- * @param wd the watch descriptor returned by mpd_inotify_source_add()
- */
-void
-mpd_inotify_source_rm(struct mpd_inotify_source *source, unsigned wd);
-
-#endif
diff --git a/src/inotify_update.c b/src/inotify_update.c
deleted file mode 100644
index 3f4a8c0c4..000000000
--- a/src/inotify_update.c
+++ /dev/null
@@ -1,374 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h" /* must be first for large file support */
-#include "inotify_update.h"
-#include "inotify_source.h"
-#include "inotify_queue.h"
-#include "database.h"
-#include "mapper.h"
-#include "path.h"
-
-#include <assert.h>
-#include <sys/inotify.h>
-#include <sys/stat.h>
-#include <stdbool.h>
-#include <string.h>
-#include <dirent.h>
-#include <errno.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "inotify"
-
-enum {
- IN_MASK = IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_DELETE_SELF
- |IN_MOVE|IN_MOVE_SELF
-#ifdef IN_ONLYDIR
- |IN_ONLYDIR
-#endif
-};
-
-struct watch_directory {
- struct watch_directory *parent;
-
- char *name;
-
- int descriptor;
-
- GList *children;
-};
-
-static struct mpd_inotify_source *inotify_source;
-
-static unsigned inotify_max_depth;
-static struct watch_directory inotify_root;
-static GTree *inotify_directories;
-
-static gint
-compare(gconstpointer a, gconstpointer b)
-{
- if (a < b)
- return -1;
- else if (a > b)
- return 1;
- else
- return 0;
-}
-
-static void
-tree_add_watch_directory(struct watch_directory *directory)
-{
- g_tree_insert(inotify_directories,
- GINT_TO_POINTER(directory->descriptor), directory);
-}
-
-static void
-tree_remove_watch_directory(struct watch_directory *directory)
-{
- G_GNUC_UNUSED
- bool found = g_tree_remove(inotify_directories,
- GINT_TO_POINTER(directory->descriptor));
- assert(found);
-}
-
-static struct watch_directory *
-tree_find_watch_directory(int wd)
-{
- return g_tree_lookup(inotify_directories, GINT_TO_POINTER(wd));
-}
-
-static void
-remove_watch_directory(struct watch_directory *directory)
-{
- assert(directory != NULL);
-
- if (directory->parent == NULL) {
- g_warning("music directory was removed - "
- "cannot continue to watch it");
- return;
- }
-
- assert(directory->parent->children != NULL);
-
- tree_remove_watch_directory(directory);
-
- while (directory->children != NULL)
- remove_watch_directory(directory->children->data);
-
- directory->parent->children =
- g_list_remove(directory->parent->children, directory);
-
- mpd_inotify_source_rm(inotify_source, directory->descriptor);
- g_free(directory->name);
- g_slice_free(struct watch_directory, directory);
-}
-
-static char *
-watch_directory_get_uri_fs(const struct watch_directory *directory)
-{
- char *parent_uri, *uri;
-
- if (directory->parent == NULL)
- return NULL;
-
- parent_uri = watch_directory_get_uri_fs(directory->parent);
- if (parent_uri == NULL)
- return g_strdup(directory->name);
-
- uri = g_strconcat(parent_uri, "/", directory->name, NULL);
- g_free(parent_uri);
-
- return uri;
-}
-
-/* we don't look at "." / ".." nor files with newlines in their name */
-static bool skip_path(const char *path)
-{
- return (path[0] == '.' && path[1] == 0) ||
- (path[0] == '.' && path[1] == '.' && path[2] == 0) ||
- strchr(path, '\n') != NULL;
-}
-
-static void
-recursive_watch_subdirectories(struct watch_directory *directory,
- const char *path_fs, unsigned depth)
-{
- GError *error = NULL;
- DIR *dir;
- struct dirent *ent;
-
- assert(directory != NULL);
- assert(depth <= inotify_max_depth);
- assert(path_fs != NULL);
-
- ++depth;
-
- if (depth > inotify_max_depth)
- return;
-
- dir = opendir(path_fs);
- if (dir == NULL) {
- g_warning("Failed to open directory %s: %s",
- path_fs, g_strerror(errno));
- return;
- }
-
- while ((ent = readdir(dir))) {
- char *child_path_fs;
- struct stat st;
- int ret;
- struct watch_directory *child;
-
- if (skip_path(ent->d_name))
- continue;
-
- child_path_fs = g_strconcat(path_fs, "/", ent->d_name, NULL);
- ret = stat(child_path_fs, &st);
- if (ret < 0) {
- g_warning("Failed to stat %s: %s",
- child_path_fs, g_strerror(errno));
- g_free(child_path_fs);
- continue;
- }
-
- if (!S_ISDIR(st.st_mode)) {
- g_free(child_path_fs);
- continue;
- }
-
- ret = mpd_inotify_source_add(inotify_source, child_path_fs,
- IN_MASK, &error);
- if (ret < 0) {
- g_warning("Failed to register %s: %s",
- child_path_fs, error->message);
- g_error_free(error);
- error = NULL;
- g_free(child_path_fs);
- continue;
- }
-
- child = tree_find_watch_directory(ret);
- if (child != NULL) {
- /* already being watched */
- g_free(child_path_fs);
- continue;
- }
-
- child = g_slice_new(struct watch_directory);
- child->parent = directory;
- child->name = g_strdup(ent->d_name);
- child->descriptor = ret;
- child->children = NULL;
-
- directory->children = g_list_prepend(directory->children,
- child);
-
- tree_add_watch_directory(child);
-
- recursive_watch_subdirectories(child, child_path_fs, depth);
- g_free(child_path_fs);
- }
-
- closedir(dir);
-}
-
-G_GNUC_PURE
-static unsigned
-watch_directory_depth(const struct watch_directory *d)
-{
- assert(d != NULL);
-
- unsigned depth = 0;
- while ((d = d->parent) != NULL)
- ++depth;
-
- return depth;
-}
-
-static void
-mpd_inotify_callback(int wd, unsigned mask,
- G_GNUC_UNUSED const char *name, G_GNUC_UNUSED void *ctx)
-{
- struct watch_directory *directory;
- char *uri_fs;
-
- /*g_debug("wd=%d mask=0x%x name='%s'", wd, mask, name);*/
-
- directory = tree_find_watch_directory(wd);
- if (directory == NULL)
- return;
-
- uri_fs = watch_directory_get_uri_fs(directory);
-
- if ((mask & (IN_DELETE_SELF|IN_MOVE_SELF)) != 0) {
- g_free(uri_fs);
- remove_watch_directory(directory);
- return;
- }
-
- if ((mask & (IN_ATTRIB|IN_CREATE|IN_MOVE)) != 0 &&
- (mask & IN_ISDIR) != 0) {
- /* a sub directory was changed: register those in
- inotify */
- const char *root = mapper_get_music_directory_fs();
- const char *path_fs;
- char *allocated = NULL;
-
- if (uri_fs != NULL)
- path_fs = allocated =
- g_strconcat(root, "/", uri_fs, NULL);
- else
- path_fs = root;
-
- recursive_watch_subdirectories(directory, path_fs,
- watch_directory_depth(directory));
- g_free(allocated);
- }
-
- if ((mask & (IN_CLOSE_WRITE|IN_MOVE|IN_DELETE)) != 0 ||
- /* at the maximum depth, we watch out for newly created
- directories */
- (watch_directory_depth(directory) == inotify_max_depth &&
- (mask & (IN_CREATE|IN_ISDIR)) == (IN_CREATE|IN_ISDIR))) {
- /* a file was changed, or a directory was
- moved/deleted: queue a database update */
- char *uri_utf8 = uri_fs != NULL
- ? fs_charset_to_utf8(uri_fs)
- : g_strdup("");
-
- if (uri_utf8 != NULL)
- /* this function will take care of freeing
- uri_utf8 */
- mpd_inotify_enqueue(uri_utf8);
- }
-
- g_free(uri_fs);
-}
-
-void
-mpd_inotify_init(unsigned max_depth)
-{
- GError *error = NULL;
-
- g_debug("initializing inotify");
-
- const char *path = mapper_get_music_directory_fs();
- if (path == NULL) {
- g_debug("no music directory configured");
- return;
- }
-
- inotify_source = mpd_inotify_source_new(mpd_inotify_callback, NULL,
- &error);
- if (inotify_source == NULL) {
- g_warning("%s", error->message);
- g_error_free(error);
- return;
- }
-
- inotify_max_depth = max_depth;
-
- inotify_root.name = g_strdup(path);
- inotify_root.descriptor = mpd_inotify_source_add(inotify_source, path,
- IN_MASK, &error);
- if (inotify_root.descriptor < 0) {
- g_warning("%s", error->message);
- g_error_free(error);
- mpd_inotify_source_free(inotify_source);
- inotify_source = NULL;
- return;
- }
-
- inotify_directories = g_tree_new(compare);
- tree_add_watch_directory(&inotify_root);
-
- recursive_watch_subdirectories(&inotify_root, path, 0);
-
- mpd_inotify_queue_init();
-
- g_debug("watching music directory");
-}
-
-static gboolean
-free_watch_directory(G_GNUC_UNUSED gpointer key, gpointer value,
- G_GNUC_UNUSED gpointer data)
-{
- struct watch_directory *directory = value;
-
- g_free(directory->name);
- g_list_free(directory->children);
-
- if (directory != &inotify_root)
- g_slice_free(struct watch_directory, directory);
-
- return false;
-}
-
-void
-mpd_inotify_finish(void)
-{
- if (inotify_source == NULL)
- return;
-
- mpd_inotify_queue_finish();
- mpd_inotify_source_free(inotify_source);
-
- g_tree_foreach(inotify_directories, free_watch_directory, NULL);
- g_tree_destroy(inotify_directories);
-}
diff --git a/src/inotify_update.h b/src/inotify_update.h
deleted file mode 100644
index ca75c0f45..000000000
--- a/src/inotify_update.h
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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_INOTIFY_UPDATE_H
-#define MPD_INOTIFY_UPDATE_H
-
-#include "check.h"
-
-#ifdef HAVE_INOTIFY_INIT
-
-void
-mpd_inotify_init(unsigned max_depth);
-
-void
-mpd_inotify_finish(void);
-
-#else /* !HAVE_INOTIFY_INIT */
-
-static inline void
-mpd_inotify_init(G_GNUC_UNUSED unsigned max_depth)
-{
-}
-
-static inline void
-mpd_inotify_finish(void)
-{
-}
-
-#endif /* !HAVE_INOTIFY_INIT */
-
-#endif
diff --git a/src/input/ArchiveInputPlugin.cxx b/src/input/ArchiveInputPlugin.cxx
new file mode 100644
index 000000000..0d856527f
--- /dev/null
+++ b/src/input/ArchiveInputPlugin.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.
+ */
+
+#include "config.h"
+#include "ArchiveInputPlugin.hxx"
+#include "ArchiveLookup.hxx"
+#include "ArchiveList.hxx"
+#include "ArchivePlugin.hxx"
+#include "ArchiveFile.hxx"
+#include "InputPlugin.hxx"
+
+#include <glib.h>
+
+/**
+ * select correct archive plugin to handle the input stream
+ * may allow stacking of archive plugins. for example for handling
+ * tar.gz a gzip handler opens file (through inputfile stream)
+ * then it opens a tar handler and sets gzip inputstream as
+ * parent_stream so tar plugin fetches file data from gzip
+ * plugin and gzip fetches file from disk
+ */
+static struct input_stream *
+input_archive_open(const char *pathname,
+ Mutex &mutex, Cond &cond,
+ GError **error_r)
+{
+ const struct archive_plugin *arplug;
+ char *archive, *filename, *suffix, *pname;
+ struct input_stream *is;
+
+ if (!g_path_is_absolute(pathname))
+ return NULL;
+
+ pname = g_strdup(pathname);
+ // archive_lookup will modify pname when true is returned
+ if (!archive_lookup(pname, &archive, &filename, &suffix)) {
+ g_debug("not an archive, lookup %s failed\n", pname);
+ g_free(pname);
+ return NULL;
+ }
+
+ //check which archive plugin to use (by ext)
+ arplug = archive_plugin_from_suffix(suffix);
+ if (!arplug) {
+ g_warning("can't handle archive %s\n",archive);
+ g_free(pname);
+ return NULL;
+ }
+
+ auto file = archive_file_open(arplug, archive, error_r);
+ if (file == NULL) {
+ g_free(pname);
+ return NULL;
+ }
+
+ //setup fileops
+ is = file->OpenStream(filename, mutex, cond, error_r);
+ g_free(pname);
+ file->Close();
+
+ return is;
+}
+
+const struct input_plugin input_plugin_archive = {
+ "archive",
+ nullptr,
+ nullptr,
+ input_archive_open,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+};
diff --git a/src/input/ArchiveInputPlugin.hxx b/src/input/ArchiveInputPlugin.hxx
new file mode 100644
index 000000000..96fcd0dd1
--- /dev/null
+++ b/src/input/ArchiveInputPlugin.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_ARCHIVE_HXX
+#define MPD_INPUT_ARCHIVE_HXX
+
+extern const struct input_plugin input_plugin_archive;
+
+#endif
diff --git a/src/input/CdioParanoiaInputPlugin.cxx b/src/input/CdioParanoiaInputPlugin.cxx
new file mode 100644
index 000000000..f0fa835b3
--- /dev/null
+++ b/src/input/CdioParanoiaInputPlugin.cxx
@@ -0,0 +1,379 @@
+/*
+ * 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.
+ */
+
+/**
+ * CD-Audio handling (requires libcdio_paranoia)
+ */
+
+#include "config.h"
+#include "CdioParanoiaInputPlugin.hxx"
+#include "InputInternal.hxx"
+#include "InputStream.hxx"
+#include "InputPlugin.hxx"
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <string.h>
+#include <stdlib.h>
+#include <glib.h>
+#include <assert.h>
+
+#include <cdio/paranoia.h>
+#include <cdio/cd_types.h>
+
+struct CdioParanoiaInputStream {
+ struct input_stream base;
+
+ cdrom_drive_t *drv;
+ CdIo_t *cdio;
+ cdrom_paranoia_t *para;
+
+ lsn_t lsn_from, lsn_to;
+ int lsn_relofs;
+
+ int trackno;
+
+ char buffer[CDIO_CD_FRAMESIZE_RAW];
+ int buffer_lsn;
+
+ CdioParanoiaInputStream(const char *uri, Mutex &mutex, Cond &cond,
+ int _trackno)
+ :base(input_plugin_cdio_paranoia, uri, mutex, cond),
+ drv(nullptr), cdio(nullptr), para(nullptr),
+ trackno(_trackno)
+ {
+ }
+
+ ~CdioParanoiaInputStream() {
+ if (para != nullptr)
+ cdio_paranoia_free(para);
+ if (drv != nullptr)
+ cdio_cddap_close_no_free_cdio(drv);
+ if (cdio != nullptr)
+ cdio_destroy(cdio);
+ }
+};
+
+static inline GQuark
+cdio_quark(void)
+{
+ return g_quark_from_static_string("cdio");
+}
+
+static void
+input_cdio_close(struct input_stream *is)
+{
+ CdioParanoiaInputStream *i = (CdioParanoiaInputStream *)is;
+
+ delete i;
+}
+
+struct cdio_uri {
+ char device[64];
+ int track;
+};
+
+static bool
+parse_cdio_uri(struct cdio_uri *dest, const char *src, GError **error_r)
+{
+ if (!g_str_has_prefix(src, "cdda://"))
+ return false;
+
+ src += 7;
+
+ if (*src == 0) {
+ /* play the whole CD in the default drive */
+ dest->device[0] = 0;
+ dest->track = -1;
+ return true;
+ }
+
+ const char *slash = strrchr(src, '/');
+ if (slash == nullptr) {
+ /* play the whole CD in the specified drive */
+ g_strlcpy(dest->device, src, sizeof(dest->device));
+ dest->track = -1;
+ return true;
+ }
+
+ size_t device_length = slash - src;
+ if (device_length >= sizeof(dest->device))
+ device_length = sizeof(dest->device) - 1;
+
+ memcpy(dest->device, src, device_length);
+ dest->device[device_length] = 0;
+
+ const char *track = slash + 1;
+
+ char *endptr;
+ dest->track = strtoul(track, &endptr, 10);
+ if (*endptr != 0) {
+ g_set_error(error_r, cdio_quark(), 0,
+ "Malformed track number");
+ return false;
+ }
+
+ if (endptr == track)
+ /* play the whole CD */
+ dest->track = -1;
+
+ return true;
+}
+
+static char *
+cdio_detect_device(void)
+{
+ char **devices = cdio_get_devices_with_cap(nullptr, CDIO_FS_AUDIO,
+ false);
+ if (devices == nullptr)
+ return nullptr;
+
+ char *device = g_strdup(devices[0]);
+ cdio_free_device_list(devices);
+
+ return device;
+}
+
+static struct input_stream *
+input_cdio_open(const char *uri,
+ Mutex &mutex, Cond &cond,
+ GError **error_r)
+{
+ struct cdio_uri parsed_uri;
+ if (!parse_cdio_uri(&parsed_uri, uri, error_r))
+ return nullptr;
+
+ CdioParanoiaInputStream *i =
+ new CdioParanoiaInputStream(uri, mutex, cond,
+ parsed_uri.track);
+
+ /* get list of CD's supporting CD-DA */
+ char *device = parsed_uri.device[0] != 0
+ ? g_strdup(parsed_uri.device)
+ : cdio_detect_device();
+ if (device == nullptr) {
+ g_set_error(error_r, cdio_quark(), 0,
+ "Unable find or access a CD-ROM drive with an audio CD in it.");
+ delete i;
+ return nullptr;
+ }
+
+ /* Found such a CD-ROM with a CD-DA loaded. Use the first drive in the list. */
+ i->cdio = cdio_open(device, DRIVER_UNKNOWN);
+ g_free(device);
+
+ i->drv = cdio_cddap_identify_cdio(i->cdio, 1, nullptr);
+
+ if ( !i->drv ) {
+ g_set_error(error_r, cdio_quark(), 0,
+ "Unable to identify audio CD disc.");
+ delete i;
+ return nullptr;
+ }
+
+ cdda_verbose_set(i->drv, CDDA_MESSAGE_FORGETIT, CDDA_MESSAGE_FORGETIT);
+
+ if ( 0 != cdio_cddap_open(i->drv) ) {
+ g_set_error(error_r, cdio_quark(), 0, "Unable to open disc.");
+ delete i;
+ return nullptr;
+ }
+
+ bool reverse_endian;
+ switch (data_bigendianp(i->drv)) {
+ case -1:
+ g_debug("cdda: drive returns unknown audio data");
+ reverse_endian = false;
+ break;
+ case 0:
+ g_debug("cdda: drive returns audio data Little Endian.");
+ reverse_endian = G_BYTE_ORDER == G_BIG_ENDIAN;
+ break;
+ case 1:
+ g_debug("cdda: drive returns audio data Big Endian.");
+ reverse_endian = G_BYTE_ORDER == G_LITTLE_ENDIAN;
+ break;
+ default:
+ g_set_error(error_r, cdio_quark(), 0,
+ "Drive returns unknown data type %d",
+ data_bigendianp(i->drv));
+ delete i;
+ return nullptr;
+ }
+
+ i->lsn_relofs = 0;
+
+ if (i->trackno >= 0) {
+ i->lsn_from = cdio_get_track_lsn(i->cdio, i->trackno);
+ i->lsn_to = cdio_get_track_last_lsn(i->cdio, i->trackno);
+ } else {
+ i->lsn_from = 0;
+ i->lsn_to = cdio_get_disc_last_lsn(i->cdio);
+ }
+
+ i->para = cdio_paranoia_init(i->drv);
+
+ /* Set reading mode for full paranoia, but allow skipping sectors. */
+ paranoia_modeset(i->para, PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP);
+
+ /* seek to beginning of the track */
+ cdio_paranoia_seek(i->para, i->lsn_from, SEEK_SET);
+
+ i->base.ready = true;
+ i->base.seekable = true;
+ i->base.size = (i->lsn_to - i->lsn_from + 1) * CDIO_CD_FRAMESIZE_RAW;
+
+ /* hack to make MPD select the "pcm" decoder plugin */
+ i->base.mime = g_strdup(reverse_endian
+ ? "audio/x-mpd-cdda-pcm-reverse"
+ : "audio/x-mpd-cdda-pcm");
+
+ return &i->base;
+}
+
+static bool
+input_cdio_seek(struct input_stream *is,
+ goffset offset, int whence, GError **error_r)
+{
+ CdioParanoiaInputStream *cis = (CdioParanoiaInputStream *)is;
+
+ /* calculate absolute offset */
+ switch (whence) {
+ case SEEK_SET:
+ break;
+ case SEEK_CUR:
+ offset += cis->base.offset;
+ break;
+ case SEEK_END:
+ offset += cis->base.size;
+ break;
+ }
+
+ if (offset < 0 || offset > cis->base.size) {
+ g_set_error(error_r, cdio_quark(), 0,
+ "Invalid offset to seek %ld (%ld)",
+ (long int)offset, (long int)cis->base.size);
+ return false;
+ }
+
+ /* simple case */
+ if (offset == cis->base.offset)
+ return true;
+
+ /* calculate current LSN */
+ cis->lsn_relofs = offset / CDIO_CD_FRAMESIZE_RAW;
+ cis->base.offset = offset;
+
+ cdio_paranoia_seek(cis->para, cis->lsn_from + cis->lsn_relofs, SEEK_SET);
+
+ return true;
+}
+
+static size_t
+input_cdio_read(struct input_stream *is, void *ptr, size_t length,
+ GError **error_r)
+{
+ CdioParanoiaInputStream *cis = (CdioParanoiaInputStream *)is;
+ size_t nbytes = 0;
+ int diff;
+ size_t len, maxwrite;
+ int16_t *rbuf;
+ char *s_err, *s_mess;
+ char *wptr = (char *) ptr;
+
+ while (length > 0) {
+
+
+ /* end of track ? */
+ if (cis->lsn_from + cis->lsn_relofs > cis->lsn_to)
+ break;
+
+ //current sector was changed ?
+ if (cis->lsn_relofs != cis->buffer_lsn) {
+ rbuf = cdio_paranoia_read(cis->para, nullptr);
+
+ s_err = cdda_errors(cis->drv);
+ if (s_err) {
+ g_warning("paranoia_read: %s", s_err );
+ free(s_err);
+ }
+ s_mess = cdda_messages(cis->drv);
+ if (s_mess) {
+ free(s_mess);
+ }
+ if (!rbuf) {
+ g_set_error(error_r, cdio_quark(), 0,
+ "paranoia read error. Stopping.");
+ return 0;
+ }
+ //store current buffer
+ memcpy(cis->buffer, rbuf, CDIO_CD_FRAMESIZE_RAW);
+ cis->buffer_lsn = cis->lsn_relofs;
+ } else {
+ //use cached sector
+ rbuf = (int16_t*) cis->buffer;
+ }
+
+ //correct offset
+ diff = cis->base.offset - cis->lsn_relofs * CDIO_CD_FRAMESIZE_RAW;
+
+ assert(diff >= 0 && diff < CDIO_CD_FRAMESIZE_RAW);
+
+ maxwrite = CDIO_CD_FRAMESIZE_RAW - diff; //# of bytes pending in current buffer
+ len = (length < maxwrite? length : maxwrite);
+
+ //skip diff bytes from this lsn
+ memcpy(wptr, ((char*)rbuf) + diff, len);
+ //update pointer
+ wptr += len;
+ nbytes += len;
+
+ //update offset
+ cis->base.offset += len;
+ cis->lsn_relofs = cis->base.offset / CDIO_CD_FRAMESIZE_RAW;
+ //update length
+ length -= len;
+ }
+
+ return nbytes;
+}
+
+static bool
+input_cdio_eof(struct input_stream *is)
+{
+ CdioParanoiaInputStream *cis = (CdioParanoiaInputStream *)is;
+
+ return (cis->lsn_from + cis->lsn_relofs > cis->lsn_to);
+}
+
+const struct input_plugin input_plugin_cdio_paranoia = {
+ "cdio_paranoia",
+ nullptr,
+ nullptr,
+ input_cdio_open,
+ input_cdio_close,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ input_cdio_read,
+ input_cdio_eof,
+ input_cdio_seek,
+};
diff --git a/src/input/CdioParanoiaInputPlugin.hxx b/src/input/CdioParanoiaInputPlugin.hxx
new file mode 100644
index 000000000..80d98b4bf
--- /dev/null
+++ b/src/input/CdioParanoiaInputPlugin.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_CDIO_PARANOIA_INPUT_PLUGIN_HXX
+#define MPD_CDIO_PARANOIA_INPUT_PLUGIN_HXX
+
+/**
+ * An input plugin based on libcdio_paranoia library.
+ */
+extern const struct input_plugin input_plugin_cdio_paranoia;
+
+#endif
diff --git a/src/input/CurlInputPlugin.cxx b/src/input/CurlInputPlugin.cxx
new file mode 100644
index 000000000..5e9cbf1d8
--- /dev/null
+++ b/src/input/CurlInputPlugin.cxx
@@ -0,0 +1,1184 @@
+/*
+ * 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 "CurlInputPlugin.hxx"
+#include "InputInternal.hxx"
+#include "InputStream.hxx"
+#include "InputPlugin.hxx"
+#include "conf.h"
+#include "Tag.hxx"
+#include "IcyMetaDataParser.hxx"
+#include "event/MultiSocketMonitor.hxx"
+#include "event/Loop.hxx"
+#include "IOThread.hxx"
+
+#include <assert.h>
+
+#if defined(WIN32)
+ #include <winsock2.h>
+#else
+ #include <sys/select.h>
+#endif
+
+#include <string.h>
+#include <errno.h>
+
+#include <list>
+#include <forward_list>
+
+#include <curl/curl.h>
+#include <glib.h>
+
+#if LIBCURL_VERSION_NUM < 0x071200
+#error libcurl is too old
+#endif
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "input_curl"
+
+/**
+ * Do not buffer more than this number of bytes. It should be a
+ * reasonable limit that doesn't make low-end machines suffer too
+ * much, but doesn't cause stuttering on high-latency lines.
+ */
+static const size_t CURL_MAX_BUFFERED = 512 * 1024;
+
+/**
+ * Resume the stream at this number of bytes after it has been paused.
+ */
+static const size_t CURL_RESUME_AT = 384 * 1024;
+
+/**
+ * Buffers created by input_curl_writefunction().
+ */
+class CurlInputBuffer {
+ /** size of the payload */
+ size_t size;
+
+ /** how much has been consumed yet? */
+ size_t consumed;
+
+ /** the payload */
+ uint8_t *data;
+
+public:
+ CurlInputBuffer(const void *_data, size_t _size)
+ :size(_size), consumed(0), data(new uint8_t[size]) {
+ memcpy(data, _data, size);
+ }
+
+ ~CurlInputBuffer() {
+ delete[] data;
+ }
+
+ CurlInputBuffer(const CurlInputBuffer &) = delete;
+ CurlInputBuffer &operator=(const CurlInputBuffer &) = delete;
+
+ const void *Begin() const {
+ return data + consumed;
+ }
+
+ size_t TotalSize() const {
+ return size;
+ }
+
+ size_t Available() const {
+ return size - consumed;
+ }
+
+ /**
+ * Mark a part of the buffer as consumed.
+ *
+ * @return false if the buffer is now empty
+ */
+ bool Consume(size_t length) {
+ assert(consumed < size);
+
+ consumed += length;
+ if (consumed < size)
+ return true;
+
+ assert(consumed == size);
+ return false;
+ }
+
+ bool Read(void *dest, size_t length) {
+ assert(consumed + length <= size);
+
+ memcpy(dest, data + consumed, length);
+ return Consume(length);
+ }
+};
+
+struct input_curl {
+ struct input_stream base;
+
+ /* some buffers which were passed to libcurl, which we have
+ too free */
+ char *range;
+ struct curl_slist *request_headers;
+
+ /** the curl handles */
+ CURL *easy;
+
+ /** list of buffers, where input_curl_writefunction() appends
+ to, and input_curl_read() reads from them */
+ std::list<CurlInputBuffer> buffers;
+
+ /**
+ * Is the connection currently paused? That happens when the
+ * buffer was getting too large. It will be unpaused when the
+ * buffer is below the threshold again.
+ */
+ bool paused;
+
+ /** error message provided by libcurl */
+ char error[CURL_ERROR_SIZE];
+
+ /** parser for icy-metadata */
+ IcyMetaDataParser icy;
+
+ /** the stream name from the icy-name response header */
+ char *meta_name;
+
+ /** the tag object ready to be requested via
+ input_stream_tag() */
+ Tag *tag;
+
+ GError *postponed_error;
+
+ input_curl(const char *url, Mutex &mutex, Cond &cond)
+ :base(input_plugin_curl, url, mutex, cond),
+ range(nullptr), request_headers(nullptr),
+ paused(false),
+ meta_name(nullptr),
+ tag(nullptr),
+ postponed_error(nullptr) {
+ }
+
+ ~input_curl();
+
+ input_curl(const input_curl &) = delete;
+ input_curl &operator=(const input_curl &) = delete;
+};
+
+/**
+ * This class monitors all CURL file descriptors.
+ */
+class CurlSockets final : private MultiSocketMonitor {
+ /**
+ * Did CURL give us a timeout? If yes, then we need to call
+ * curl_multi_perform(), even if there was no event on any
+ * file descriptor.
+ */
+ bool have_timeout;
+
+ /**
+ * The absolute time stamp when the timeout expires.
+ */
+ gint64 absolute_timeout;
+
+public:
+ CurlSockets(EventLoop &_loop)
+ :MultiSocketMonitor(_loop) {}
+
+ using MultiSocketMonitor::InvalidateSockets;
+
+private:
+ void UpdateSockets();
+
+ virtual void PrepareSockets(gcc_unused gint *timeout_r) override;
+ virtual bool CheckSockets() const override;
+ virtual void DispatchSockets() override;
+};
+
+/** libcurl should accept "ICY 200 OK" */
+static struct curl_slist *http_200_aliases;
+
+/** HTTP proxy settings */
+static const char *proxy, *proxy_user, *proxy_password;
+static unsigned proxy_port;
+
+static struct {
+ CURLM *multi;
+
+ /**
+ * A linked list of all active HTTP requests. An active
+ * request is one that doesn't have the "eof" flag set.
+ */
+ std::forward_list<input_curl *> requests;
+
+ CurlSockets *sockets;
+} curl;
+
+static inline GQuark
+curl_quark(void)
+{
+ return g_quark_from_static_string("curl");
+}
+
+/**
+ * Find a request by its CURL "easy" handle.
+ *
+ * Runs in the I/O thread. No lock needed.
+ */
+static struct input_curl *
+input_curl_find_request(CURL *easy)
+{
+ assert(io_thread_inside());
+
+ for (auto c : curl.requests)
+ if (c->easy == easy)
+ return c;
+
+ return NULL;
+}
+
+static gpointer
+input_curl_resume(gpointer data)
+{
+ assert(io_thread_inside());
+
+ struct input_curl *c = (struct input_curl *)data;
+
+ if (c->paused) {
+ c->paused = false;
+ curl_easy_pause(c->easy, CURLPAUSE_CONT);
+ }
+
+ return NULL;
+}
+
+/**
+ * Calculates the GLib event bit mask for one file descriptor,
+ * obtained from three #fd_set objects filled by curl_multi_fdset().
+ */
+static unsigned
+input_curl_fd_events(int fd, fd_set *rfds, fd_set *wfds, fd_set *efds)
+{
+ gushort events = 0;
+
+ if (FD_ISSET(fd, rfds)) {
+ events |= G_IO_IN | G_IO_HUP | G_IO_ERR;
+ FD_CLR(fd, rfds);
+ }
+
+ if (FD_ISSET(fd, wfds)) {
+ events |= G_IO_OUT | G_IO_ERR;
+ FD_CLR(fd, wfds);
+ }
+
+ if (FD_ISSET(fd, efds)) {
+ events |= G_IO_HUP | G_IO_ERR;
+ FD_CLR(fd, efds);
+ }
+
+ return events;
+}
+
+/**
+ * Updates all registered GPollFD objects, unregisters old ones,
+ * registers new ones.
+ *
+ * Runs in the I/O thread. No lock needed.
+ */
+void
+CurlSockets::UpdateSockets()
+{
+ assert(io_thread_inside());
+
+ fd_set rfds, wfds, efds;
+
+ FD_ZERO(&rfds);
+ FD_ZERO(&wfds);
+ FD_ZERO(&efds);
+
+ int max_fd;
+ CURLMcode mcode = curl_multi_fdset(curl.multi, &rfds, &wfds,
+ &efds, &max_fd);
+ if (mcode != CURLM_OK) {
+ g_warning("curl_multi_fdset() failed: %s\n",
+ curl_multi_strerror(mcode));
+ return;
+ }
+
+ UpdateSocketList([&rfds, &wfds, &efds](int fd){
+ return input_curl_fd_events(fd, &rfds,
+ &wfds, &efds);
+ });
+
+ for (int fd = 0; fd <= max_fd; ++fd) {
+ unsigned events = input_curl_fd_events(fd, &rfds, &wfds, &efds);
+ if (events != 0)
+ AddSocket(fd, events);
+ }
+}
+
+/**
+ * Runs in the I/O thread. No lock needed.
+ */
+static bool
+input_curl_easy_add(struct input_curl *c, GError **error_r)
+{
+ assert(io_thread_inside());
+ assert(c != NULL);
+ assert(c->easy != NULL);
+ assert(input_curl_find_request(c->easy) == NULL);
+
+ curl.requests.push_front(c);
+
+ CURLMcode mcode = curl_multi_add_handle(curl.multi, c->easy);
+ if (mcode != CURLM_OK) {
+ g_set_error(error_r, curl_quark(), mcode,
+ "curl_multi_add_handle() failed: %s",
+ curl_multi_strerror(mcode));
+ return false;
+ }
+
+ curl.sockets->InvalidateSockets();
+
+ return true;
+}
+
+struct easy_add_params {
+ struct input_curl *c;
+ GError **error_r;
+};
+
+static gpointer
+input_curl_easy_add_callback(gpointer data)
+{
+ const struct easy_add_params *params =
+ (const struct easy_add_params *)data;
+
+ bool success = input_curl_easy_add(params->c, params->error_r);
+ return GUINT_TO_POINTER(success);
+}
+
+/**
+ * Call input_curl_easy_add() in the I/O thread. May be called from
+ * any thread. Caller must not hold a mutex.
+ */
+static bool
+input_curl_easy_add_indirect(struct input_curl *c, GError **error_r)
+{
+ assert(c != NULL);
+ assert(c->easy != NULL);
+
+ struct easy_add_params params = {
+ c,
+ error_r,
+ };
+
+ gpointer result =
+ io_thread_call(input_curl_easy_add_callback, &params);
+ return GPOINTER_TO_UINT(result);
+}
+
+/**
+ * Frees the current "libcurl easy" handle, and everything associated
+ * with it.
+ *
+ * Runs in the I/O thread.
+ */
+static void
+input_curl_easy_free(struct input_curl *c)
+{
+ assert(io_thread_inside());
+ assert(c != NULL);
+
+ if (c->easy == NULL)
+ return;
+
+ curl.requests.remove(c);
+
+ curl_multi_remove_handle(curl.multi, c->easy);
+ curl_easy_cleanup(c->easy);
+ c->easy = NULL;
+
+ curl_slist_free_all(c->request_headers);
+ c->request_headers = NULL;
+
+ g_free(c->range);
+ c->range = NULL;
+}
+
+static gpointer
+input_curl_easy_free_callback(gpointer data)
+{
+ struct input_curl *c = (struct input_curl *)data;
+
+ input_curl_easy_free(c);
+ curl.sockets->InvalidateSockets();
+
+ return NULL;
+}
+
+/**
+ * Frees the current "libcurl easy" handle, and everything associated
+ * with it.
+ *
+ * The mutex must not be locked.
+ */
+static void
+input_curl_easy_free_indirect(struct input_curl *c)
+{
+ io_thread_call(input_curl_easy_free_callback, c);
+ assert(c->easy == NULL);
+}
+
+/**
+ * Abort and free all HTTP requests.
+ *
+ * Runs in the I/O thread. The caller must not hold locks.
+ */
+static void
+input_curl_abort_all_requests(GError *error)
+{
+ assert(io_thread_inside());
+ assert(error != NULL);
+
+ while (!curl.requests.empty()) {
+ struct input_curl *c = curl.requests.front();
+ assert(c->postponed_error == NULL);
+
+ input_curl_easy_free(c);
+
+ const ScopeLock protect(c->base.mutex);
+
+ c->postponed_error = g_error_copy(error);
+ c->base.ready = true;
+
+ c->base.cond.broadcast();
+ }
+
+ g_error_free(error);
+
+}
+
+/**
+ * A HTTP request is finished.
+ *
+ * Runs in the I/O thread. The caller must not hold locks.
+ */
+static void
+input_curl_request_done(struct input_curl *c, CURLcode result, long status)
+{
+ assert(io_thread_inside());
+ assert(c != NULL);
+ assert(c->easy == NULL);
+ assert(c->postponed_error == NULL);
+
+ const ScopeLock protect(c->base.mutex);
+
+ if (result != CURLE_OK) {
+ c->postponed_error = g_error_new(curl_quark(), result,
+ "curl failed: %s",
+ c->error);
+ } else if (status < 200 || status >= 300) {
+ c->postponed_error = g_error_new(curl_quark(), 0,
+ "got HTTP status %ld",
+ status);
+ }
+
+ c->base.ready = true;
+
+ c->base.cond.broadcast();
+}
+
+static void
+input_curl_handle_done(CURL *easy_handle, CURLcode result)
+{
+ struct input_curl *c = input_curl_find_request(easy_handle);
+ assert(c != NULL);
+
+ long status = 0;
+ curl_easy_getinfo(easy_handle, CURLINFO_RESPONSE_CODE, &status);
+
+ input_curl_easy_free(c);
+ input_curl_request_done(c, result, status);
+}
+
+/**
+ * Check for finished HTTP responses.
+ *
+ * Runs in the I/O thread. The caller must not hold locks.
+ */
+static void
+input_curl_info_read(void)
+{
+ assert(io_thread_inside());
+
+ CURLMsg *msg;
+ int msgs_in_queue;
+
+ while ((msg = curl_multi_info_read(curl.multi,
+ &msgs_in_queue)) != NULL) {
+ if (msg->msg == CURLMSG_DONE)
+ input_curl_handle_done(msg->easy_handle, msg->data.result);
+ }
+}
+
+/**
+ * Give control to CURL.
+ *
+ * Runs in the I/O thread. The caller must not hold locks.
+ */
+static bool
+input_curl_perform(void)
+{
+ assert(io_thread_inside());
+
+ CURLMcode mcode;
+
+ do {
+ int running_handles;
+ mcode = curl_multi_perform(curl.multi, &running_handles);
+ } while (mcode == CURLM_CALL_MULTI_PERFORM);
+
+ if (mcode != CURLM_OK && mcode != CURLM_CALL_MULTI_PERFORM) {
+ GError *error = g_error_new(curl_quark(), mcode,
+ "curl_multi_perform() failed: %s",
+ curl_multi_strerror(mcode));
+ input_curl_abort_all_requests(error);
+ return false;
+ }
+
+ return true;
+}
+
+void
+CurlSockets::PrepareSockets(gint *timeout_r)
+{
+ UpdateSockets();
+
+ have_timeout = false;
+
+ long timeout2;
+ CURLMcode mcode = curl_multi_timeout(curl.multi, &timeout2);
+ if (mcode == CURLM_OK) {
+ if (timeout2 >= 0)
+ absolute_timeout = GetTime() + timeout2 * 1000;
+
+ if (timeout2 >= 0 && timeout2 < 10)
+ /* CURL 7.21.1 likes to report "timeout=0",
+ which means we're running in a busy loop.
+ Quite a bad idea to waste so much CPU.
+ Let's use a lower limit of 10ms. */
+ timeout2 = 10;
+
+ *timeout_r = timeout2;
+
+ have_timeout = timeout2 >= 0;
+ } else
+ g_warning("curl_multi_timeout() failed: %s\n",
+ curl_multi_strerror(mcode));
+}
+
+bool
+CurlSockets::CheckSockets() const
+{
+ /* when a timeout has expired, we need to call
+ curl_multi_perform(), even if there was no file descriptor
+ event */
+ return have_timeout && GetTime() >= absolute_timeout;
+}
+
+void
+CurlSockets::DispatchSockets()
+{
+ if (input_curl_perform())
+ input_curl_info_read();
+}
+
+/*
+ * input_plugin methods
+ *
+ */
+
+static bool
+input_curl_init(const config_param &param,
+ G_GNUC_UNUSED GError **error_r)
+{
+ CURLcode code = curl_global_init(CURL_GLOBAL_ALL);
+ if (code != CURLE_OK) {
+ g_set_error(error_r, curl_quark(), code,
+ "curl_global_init() failed: %s\n",
+ curl_easy_strerror(code));
+ return false;
+ }
+
+ http_200_aliases = curl_slist_append(http_200_aliases, "ICY 200 OK");
+
+ proxy = param.GetBlockValue("proxy");
+ proxy_port = param.GetBlockValue("proxy_port", 0u);
+ proxy_user = param.GetBlockValue("proxy_user");
+ proxy_password = param.GetBlockValue("proxy_password");
+
+ if (proxy == NULL) {
+ /* deprecated proxy configuration */
+ proxy = config_get_string(CONF_HTTP_PROXY_HOST, NULL);
+ proxy_port = config_get_positive(CONF_HTTP_PROXY_PORT, 0);
+ proxy_user = config_get_string(CONF_HTTP_PROXY_USER, NULL);
+ proxy_password = config_get_string(CONF_HTTP_PROXY_PASSWORD,
+ "");
+ }
+
+ curl.multi = curl_multi_init();
+ if (curl.multi == NULL) {
+ g_set_error(error_r, curl_quark(), 0,
+ "curl_multi_init() failed");
+ return false;
+ }
+
+ curl.sockets = new CurlSockets(io_thread_get());
+
+ return true;
+}
+
+static gpointer
+curl_destroy_sources(G_GNUC_UNUSED gpointer data)
+{
+ delete curl.sockets;
+
+ return NULL;
+}
+
+static void
+input_curl_finish(void)
+{
+ assert(curl.requests.empty());
+
+ io_thread_call(curl_destroy_sources, NULL);
+
+ curl_multi_cleanup(curl.multi);
+
+ curl_slist_free_all(http_200_aliases);
+
+ curl_global_cleanup();
+}
+
+/**
+ * Determine the total sizes of all buffers, including portions that
+ * have already been consumed.
+ *
+ * The caller must lock the mutex.
+ */
+G_GNUC_PURE
+static size_t
+curl_total_buffer_size(const struct input_curl *c)
+{
+ size_t total = 0;
+
+ for (const auto &i : c->buffers)
+ total += i.TotalSize();
+
+ return total;
+}
+
+input_curl::~input_curl()
+{
+ delete tag;
+
+ g_free(meta_name);
+
+ input_curl_easy_free_indirect(this);
+
+ if (postponed_error != NULL)
+ g_error_free(postponed_error);
+}
+
+static bool
+input_curl_check(struct input_stream *is, GError **error_r)
+{
+ struct input_curl *c = (struct input_curl *)is;
+
+ bool success = c->postponed_error == NULL;
+ if (!success) {
+ g_propagate_error(error_r, c->postponed_error);
+ c->postponed_error = NULL;
+ }
+
+ return success;
+}
+
+static Tag *
+input_curl_tag(struct input_stream *is)
+{
+ struct input_curl *c = (struct input_curl *)is;
+ Tag *tag = c->tag;
+
+ c->tag = NULL;
+ return tag;
+}
+
+static bool
+fill_buffer(struct input_curl *c, GError **error_r)
+{
+ while (c->easy != NULL && c->buffers.empty())
+ c->base.cond.wait(c->base.mutex);
+
+ if (c->postponed_error != NULL) {
+ g_propagate_error(error_r, c->postponed_error);
+ c->postponed_error = NULL;
+ return false;
+ }
+
+ return !c->buffers.empty();
+}
+
+static size_t
+read_from_buffer(IcyMetaDataParser &icy, std::list<CurlInputBuffer> &buffers,
+ void *dest0, size_t length)
+{
+ auto &buffer = buffers.front();
+ uint8_t *dest = (uint8_t *)dest0;
+ size_t nbytes = 0;
+
+ if (length > buffer.Available())
+ length = buffer.Available();
+
+ while (true) {
+ size_t chunk;
+
+ chunk = icy.Data(length);
+ if (chunk > 0) {
+ const bool empty = !buffer.Read(dest, chunk);
+
+ nbytes += chunk;
+ dest += chunk;
+ length -= chunk;
+
+ if (empty) {
+ buffers.pop_front();
+ break;
+ }
+
+ if (length == 0)
+ break;
+ }
+
+ chunk = icy.Meta(buffer.Begin(), length);
+ if (chunk > 0) {
+ const bool empty = !buffer.Consume(chunk);
+
+ length -= chunk;
+
+ if (empty) {
+ buffers.pop_front();
+ break;
+ }
+
+ if (length == 0)
+ break;
+ }
+ }
+
+ return nbytes;
+}
+
+static void
+copy_icy_tag(struct input_curl *c)
+{
+ Tag *tag = c->icy.ReadTag();
+
+ if (tag == NULL)
+ return;
+
+ delete c->tag;
+
+ if (c->meta_name != NULL && !tag->HasType(TAG_NAME))
+ tag->AddItem(TAG_NAME, c->meta_name);
+
+ c->tag = tag;
+}
+
+static bool
+input_curl_available(struct input_stream *is)
+{
+ struct input_curl *c = (struct input_curl *)is;
+
+ return c->postponed_error != NULL || c->easy == NULL ||
+ !c->buffers.empty();
+}
+
+static size_t
+input_curl_read(struct input_stream *is, void *ptr, size_t size,
+ GError **error_r)
+{
+ struct input_curl *c = (struct input_curl *)is;
+ bool success;
+ size_t nbytes = 0;
+ char *dest = (char *)ptr;
+
+ do {
+ /* fill the buffer */
+
+ success = fill_buffer(c, error_r);
+ if (!success)
+ return 0;
+
+ /* send buffer contents */
+
+ while (size > 0 && !c->buffers.empty()) {
+ size_t copy = read_from_buffer(c->icy, c->buffers,
+ dest + nbytes, size);
+
+ nbytes += copy;
+ size -= copy;
+ }
+ } while (nbytes == 0);
+
+ if (c->icy.IsDefined())
+ copy_icy_tag(c);
+
+ is->offset += (goffset)nbytes;
+
+ if (c->paused && curl_total_buffer_size(c) < CURL_RESUME_AT) {
+ c->base.mutex.unlock();
+ io_thread_call(input_curl_resume, c);
+ c->base.mutex.lock();
+ }
+
+ return nbytes;
+}
+
+static void
+input_curl_close(struct input_stream *is)
+{
+ struct input_curl *c = (struct input_curl *)is;
+
+ delete c;
+}
+
+static bool
+input_curl_eof(G_GNUC_UNUSED struct input_stream *is)
+{
+ struct input_curl *c = (struct input_curl *)is;
+
+ return c->easy == NULL && c->buffers.empty();
+}
+
+/** called by curl when new data is available */
+static size_t
+input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream)
+{
+ struct input_curl *c = (struct input_curl *)stream;
+ char name[64];
+
+ size *= nmemb;
+
+ const char *header = (const char *)ptr;
+ const char *end = header + size;
+
+ const char *value = (const char *)memchr(header, ':', size);
+ if (value == NULL || (size_t)(value - header) >= sizeof(name))
+ return size;
+
+ memcpy(name, header, value - header);
+ name[value - header] = 0;
+
+ /* skip the colon */
+
+ ++value;
+
+ /* strip the value */
+
+ while (value < end && g_ascii_isspace(*value))
+ ++value;
+
+ while (end > value && g_ascii_isspace(end[-1]))
+ --end;
+
+ if (g_ascii_strcasecmp(name, "accept-ranges") == 0) {
+ /* a stream with icy-metadata is not seekable */
+ if (!c->icy.IsDefined())
+ c->base.seekable = true;
+ } else if (g_ascii_strcasecmp(name, "content-length") == 0) {
+ char buffer[64];
+
+ if ((size_t)(end - header) >= sizeof(buffer))
+ return size;
+
+ memcpy(buffer, value, end - value);
+ buffer[end - value] = 0;
+
+ c->base.size = c->base.offset + g_ascii_strtoull(buffer, NULL, 10);
+ } else if (g_ascii_strcasecmp(name, "content-type") == 0) {
+ c->base.mime.assign(value, end);
+ } else if (g_ascii_strcasecmp(name, "icy-name") == 0 ||
+ g_ascii_strcasecmp(name, "ice-name") == 0 ||
+ g_ascii_strcasecmp(name, "x-audiocast-name") == 0) {
+ g_free(c->meta_name);
+ c->meta_name = g_strndup(value, end - value);
+
+ delete c->tag;
+
+ c->tag = new Tag();
+ c->tag->AddItem(TAG_NAME, c->meta_name);
+ } else if (g_ascii_strcasecmp(name, "icy-metaint") == 0) {
+ char buffer[64];
+ size_t icy_metaint;
+
+ if ((size_t)(end - header) >= sizeof(buffer) ||
+ c->icy.IsDefined())
+ return size;
+
+ memcpy(buffer, value, end - value);
+ buffer[end - value] = 0;
+
+ icy_metaint = g_ascii_strtoull(buffer, NULL, 10);
+ g_debug("icy-metaint=%zu", icy_metaint);
+
+ if (icy_metaint > 0) {
+ c->icy.Start(icy_metaint);
+
+ /* a stream with icy-metadata is not
+ seekable */
+ c->base.seekable = false;
+ }
+ }
+
+ return size;
+}
+
+/** called by curl when new data is available */
+static size_t
+input_curl_writefunction(void *ptr, size_t size, size_t nmemb, void *stream)
+{
+ struct input_curl *c = (struct input_curl *)stream;
+
+ size *= nmemb;
+ if (size == 0)
+ return 0;
+
+ const ScopeLock protect(c->base.mutex);
+
+ if (curl_total_buffer_size(c) + size >= CURL_MAX_BUFFERED) {
+ c->paused = true;
+ return CURL_WRITEFUNC_PAUSE;
+ }
+
+ c->buffers.emplace_back(ptr, size);
+ c->base.ready = true;
+
+ c->base.cond.broadcast();
+ return size;
+}
+
+static bool
+input_curl_easy_init(struct input_curl *c, GError **error_r)
+{
+ CURLcode code;
+
+ c->easy = curl_easy_init();
+ if (c->easy == NULL) {
+ g_set_error(error_r, curl_quark(), 0,
+ "curl_easy_init() failed");
+ return false;
+ }
+
+ curl_easy_setopt(c->easy, CURLOPT_USERAGENT,
+ "Music Player Daemon " VERSION);
+ curl_easy_setopt(c->easy, CURLOPT_HEADERFUNCTION,
+ input_curl_headerfunction);
+ curl_easy_setopt(c->easy, CURLOPT_WRITEHEADER, c);
+ curl_easy_setopt(c->easy, CURLOPT_WRITEFUNCTION,
+ input_curl_writefunction);
+ curl_easy_setopt(c->easy, CURLOPT_WRITEDATA, c);
+ curl_easy_setopt(c->easy, CURLOPT_HTTP200ALIASES, http_200_aliases);
+ curl_easy_setopt(c->easy, CURLOPT_FOLLOWLOCATION, 1);
+ curl_easy_setopt(c->easy, CURLOPT_NETRC, 1);
+ curl_easy_setopt(c->easy, CURLOPT_MAXREDIRS, 5);
+ curl_easy_setopt(c->easy, CURLOPT_FAILONERROR, true);
+ curl_easy_setopt(c->easy, CURLOPT_ERRORBUFFER, c->error);
+ curl_easy_setopt(c->easy, CURLOPT_NOPROGRESS, 1l);
+ curl_easy_setopt(c->easy, CURLOPT_NOSIGNAL, 1l);
+ curl_easy_setopt(c->easy, CURLOPT_CONNECTTIMEOUT, 10l);
+
+ if (proxy != NULL)
+ curl_easy_setopt(c->easy, CURLOPT_PROXY, proxy);
+
+ if (proxy_port > 0)
+ curl_easy_setopt(c->easy, CURLOPT_PROXYPORT, (long)proxy_port);
+
+ if (proxy_user != NULL && proxy_password != NULL) {
+ char *proxy_auth_str =
+ g_strconcat(proxy_user, ":", proxy_password, NULL);
+ curl_easy_setopt(c->easy, CURLOPT_PROXYUSERPWD, proxy_auth_str);
+ g_free(proxy_auth_str);
+ }
+
+ code = curl_easy_setopt(c->easy, CURLOPT_URL, c->base.uri.c_str());
+ if (code != CURLE_OK) {
+ g_set_error(error_r, curl_quark(), code,
+ "curl_easy_setopt() failed: %s",
+ curl_easy_strerror(code));
+ return false;
+ }
+
+ c->request_headers = NULL;
+ c->request_headers = curl_slist_append(c->request_headers,
+ "Icy-Metadata: 1");
+ curl_easy_setopt(c->easy, CURLOPT_HTTPHEADER, c->request_headers);
+
+ return true;
+}
+
+static bool
+input_curl_seek(struct input_stream *is, goffset offset, int whence,
+ GError **error_r)
+{
+ struct input_curl *c = (struct input_curl *)is;
+ bool ret;
+
+ assert(is->ready);
+
+ if (whence == SEEK_SET && offset == is->offset)
+ /* no-op */
+ return true;
+
+ if (!is->seekable)
+ return false;
+
+ /* calculate the absolute offset */
+
+ switch (whence) {
+ case SEEK_SET:
+ break;
+
+ case SEEK_CUR:
+ offset += is->offset;
+ break;
+
+ case SEEK_END:
+ if (is->size < 0)
+ /* stream size is not known */
+ return false;
+
+ offset += is->size;
+ break;
+
+ default:
+ return false;
+ }
+
+ if (offset < 0)
+ return false;
+
+ /* check if we can fast-forward the buffer */
+
+ while (offset > is->offset && !c->buffers.empty()) {
+ auto &buffer = c->buffers.front();
+ size_t length = buffer.Available();
+ if (offset - is->offset < (goffset)length)
+ length = offset - is->offset;
+
+ const bool empty = !buffer.Consume(length);
+ if (empty)
+ c->buffers.pop_front();
+
+ is->offset += length;
+ }
+
+ if (offset == is->offset)
+ return true;
+
+ /* close the old connection and open a new one */
+
+ c->base.mutex.unlock();
+
+ input_curl_easy_free_indirect(c);
+ c->buffers.clear();
+
+ is->offset = offset;
+ if (is->offset == is->size) {
+ /* seek to EOF: simulate empty result; avoid
+ triggering a "416 Requested Range Not Satisfiable"
+ response */
+ return true;
+ }
+
+ ret = input_curl_easy_init(c, error_r);
+ if (!ret)
+ return false;
+
+ /* send the "Range" header */
+
+ if (is->offset > 0) {
+ c->range = g_strdup_printf("%lld-", (long long)is->offset);
+ curl_easy_setopt(c->easy, CURLOPT_RANGE, c->range);
+ }
+
+ c->base.ready = false;
+
+ if (!input_curl_easy_add_indirect(c, error_r))
+ return false;
+
+ c->base.mutex.lock();
+
+ while (!c->base.ready)
+ c->base.cond.wait(c->base.mutex);
+
+ if (c->postponed_error != NULL) {
+ g_propagate_error(error_r, c->postponed_error);
+ c->postponed_error = NULL;
+ return false;
+ }
+
+ return true;
+}
+
+static struct input_stream *
+input_curl_open(const char *url, Mutex &mutex, Cond &cond,
+ GError **error_r)
+{
+ if (strncmp(url, "http://", 7) != 0)
+ return NULL;
+
+ struct input_curl *c = new input_curl(url, mutex, cond);
+
+ if (!input_curl_easy_init(c, error_r)) {
+ delete c;
+ return NULL;
+ }
+
+ if (!input_curl_easy_add_indirect(c, error_r)) {
+ delete c;
+ return NULL;
+ }
+
+ return &c->base;
+}
+
+const struct input_plugin input_plugin_curl = {
+ "curl",
+ input_curl_init,
+ input_curl_finish,
+ input_curl_open,
+ input_curl_close,
+ input_curl_check,
+ nullptr,
+ input_curl_tag,
+ input_curl_available,
+ input_curl_read,
+ input_curl_eof,
+ input_curl_seek,
+};
diff --git a/src/input/CurlInputPlugin.hxx b/src/input/CurlInputPlugin.hxx
new file mode 100644
index 000000000..20d1309d8
--- /dev/null
+++ b/src/input/CurlInputPlugin.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_INPUT_CURL_HXX
+#define MPD_INPUT_CURL_HXX
+
+struct input_stream;
+
+extern const struct input_plugin input_plugin_curl;
+
+#endif
diff --git a/src/input/DespotifyInputPlugin.cxx b/src/input/DespotifyInputPlugin.cxx
new file mode 100644
index 000000000..18e896608
--- /dev/null
+++ b/src/input/DespotifyInputPlugin.cxx
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2011-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 "DespotifyInputPlugin.hxx"
+#include "DespotifyUtils.hxx"
+#include "InputInternal.hxx"
+#include "InputStream.hxx"
+#include "InputPlugin.hxx"
+#include "Tag.hxx"
+
+extern "C" {
+#include <despotify.h>
+}
+
+#include <glib.h>
+
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#include <stdio.h>
+
+struct DespotifyInputStream {
+ struct input_stream base;
+
+ struct despotify_session *session;
+ struct ds_track *track;
+ Tag *tag;
+ struct ds_pcm_data pcm;
+ size_t len_available;
+ bool eof;
+
+ DespotifyInputStream(const char *uri,
+ Mutex &mutex, Cond &cond,
+ despotify_session *_session,
+ ds_track *_track)
+ :base(input_plugin_despotify, uri, mutex, cond),
+ session(_session), track(_track),
+ tag(mpd_despotify_tag_from_track(track)),
+ len_available(0), eof(false) {
+
+ memset(&pcm, 0, sizeof(pcm));
+
+ /* Despotify outputs pcm data */
+ base.mime = g_strdup("audio/x-mpd-cdda-pcm");
+ base.ready = true;
+ }
+
+ ~DespotifyInputStream() {
+ delete tag;
+
+ despotify_free_track(track);
+ }
+};
+
+static void
+refill_buffer(DespotifyInputStream *ctx)
+{
+ /* Wait until there is data */
+ while (1) {
+ int rc = despotify_get_pcm(ctx->session, &ctx->pcm);
+
+ if (rc == 0 && ctx->pcm.len) {
+ ctx->len_available = ctx->pcm.len;
+ break;
+ }
+ if (ctx->eof == true)
+ break;
+
+ if (rc < 0) {
+ g_debug("despotify_get_pcm error\n");
+ ctx->eof = true;
+ break;
+ }
+
+ /* Wait a while until next iteration */
+ usleep(50 * 1000);
+ }
+}
+
+static void callback(G_GNUC_UNUSED struct despotify_session* ds,
+ int sig, G_GNUC_UNUSED void* data, void* callback_data)
+{
+ DespotifyInputStream *ctx = (DespotifyInputStream *)callback_data;
+
+ switch (sig) {
+ case DESPOTIFY_NEW_TRACK:
+ break;
+
+ case DESPOTIFY_TIME_TELL:
+ break;
+
+ case DESPOTIFY_TRACK_PLAY_ERROR:
+ g_debug("Track play error\n");
+ ctx->eof = true;
+ ctx->len_available = 0;
+ break;
+
+ case DESPOTIFY_END_OF_PLAYLIST:
+ ctx->eof = true;
+ g_debug("End of playlist: %d\n", ctx->eof);
+ break;
+ }
+}
+
+
+static struct input_stream *
+input_despotify_open(const char *url,
+ Mutex &mutex, Cond &cond,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct despotify_session *session;
+ struct ds_link *ds_link;
+ struct ds_track *track;
+
+ if (!g_str_has_prefix(url, "spt://"))
+ return NULL;
+
+ session = mpd_despotify_get_session();
+ if (!session)
+ return NULL;
+
+ ds_link = despotify_link_from_uri(url + 6);
+ if (!ds_link) {
+ g_debug("Can't find %s\n", url);
+ return NULL;
+ }
+ if (ds_link->type != LINK_TYPE_TRACK) {
+ despotify_free_link(ds_link);
+ return NULL;
+ }
+
+ track = despotify_link_get_track(session, ds_link);
+ despotify_free_link(ds_link);
+ if (!track)
+ return NULL;
+
+ DespotifyInputStream *ctx =
+ new DespotifyInputStream(url, mutex, cond,
+ session, track);
+
+ if (!mpd_despotify_register_callback(callback, ctx)) {
+ delete ctx;
+ return NULL;
+ }
+
+ if (despotify_play(ctx->session, ctx->track, false) == false) {
+ mpd_despotify_unregister_callback(callback);
+ delete ctx;
+ return NULL;
+ }
+
+ return &ctx->base;
+}
+
+static size_t
+input_despotify_read(struct input_stream *is, void *ptr, size_t size,
+ G_GNUC_UNUSED GError **error_r)
+{
+ DespotifyInputStream *ctx = (DespotifyInputStream *)is;
+ size_t to_cpy = size;
+
+ if (ctx->len_available == 0)
+ refill_buffer(ctx);
+
+ if (ctx->len_available < size)
+ to_cpy = ctx->len_available;
+ memcpy(ptr, ctx->pcm.buf, to_cpy);
+ ctx->len_available -= to_cpy;
+
+ is->offset += to_cpy;
+
+ return to_cpy;
+}
+
+static void
+input_despotify_close(struct input_stream *is)
+{
+ DespotifyInputStream *ctx = (DespotifyInputStream *)is;
+
+ mpd_despotify_unregister_callback(callback);
+ delete ctx;
+}
+
+static bool
+input_despotify_eof(struct input_stream *is)
+{
+ DespotifyInputStream *ctx = (DespotifyInputStream *)is;
+
+ return ctx->eof;
+}
+
+static bool
+input_despotify_seek(G_GNUC_UNUSED struct input_stream *is,
+ G_GNUC_UNUSED goffset offset, G_GNUC_UNUSED int whence,
+ G_GNUC_UNUSED GError **error_r)
+{
+ return false;
+}
+
+static Tag *
+input_despotify_tag(struct input_stream *is)
+{
+ DespotifyInputStream *ctx = (DespotifyInputStream *)is;
+ Tag *tag = ctx->tag;
+
+ ctx->tag = NULL;
+
+ return tag;
+}
+
+const struct input_plugin input_plugin_despotify = {
+ "spt",
+ nullptr,
+ nullptr,
+ input_despotify_open,
+ input_despotify_close,
+ nullptr,
+ nullptr,
+ .tag = input_despotify_tag,
+ nullptr,
+ input_despotify_read,
+ input_despotify_eof,
+ input_despotify_seek,
+};
diff --git a/src/input/DespotifyInputPlugin.hxx b/src/input/DespotifyInputPlugin.hxx
new file mode 100644
index 000000000..00d699408
--- /dev/null
+++ b/src/input/DespotifyInputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2011-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 INPUT_DESPOTIFY_HXX
+#define INPUT_DESPOTIFY_HXX
+
+extern const struct input_plugin input_plugin_despotify;
+
+#endif
diff --git a/src/input/FfmpegInputPlugin.cxx b/src/input/FfmpegInputPlugin.cxx
new file mode 100644
index 000000000..3dad28730
--- /dev/null
+++ b/src/input/FfmpegInputPlugin.cxx
@@ -0,0 +1,184 @@
+/*
+ * 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.
+ */
+
+/* necessary because libavutil/common.h uses UINT64_C */
+#define __STDC_CONSTANT_MACROS
+
+#include "config.h"
+#include "FfmpegInputPlugin.hxx"
+#include "InputInternal.hxx"
+#include "InputStream.hxx"
+#include "InputPlugin.hxx"
+
+extern "C" {
+#include <libavutil/avutil.h>
+#include <libavformat/avio.h>
+#include <libavformat/avformat.h>
+}
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "input_ffmpeg"
+
+struct FfmpegInputStream {
+ struct input_stream base;
+
+ AVIOContext *h;
+
+ bool eof;
+
+ FfmpegInputStream(const char *uri, Mutex &mutex, Cond &cond,
+ AVIOContext *_h)
+ :base(input_plugin_ffmpeg, uri, mutex, cond),
+ h(_h), eof(false) {
+ base.ready = true;
+ base.seekable = (h->seekable & AVIO_SEEKABLE_NORMAL) != 0;
+ base.size = avio_size(h);
+
+ /* hack to make MPD select the "ffmpeg" decoder plugin
+ - since avio.h doesn't tell us the MIME type of the
+ resource, we can't select a decoder plugin, but the
+ "ffmpeg" plugin is quite good at auto-detection */
+ base.mime = g_strdup("audio/x-mpd-ffmpeg");
+ }
+
+ ~FfmpegInputStream() {
+ avio_close(h);
+ }
+};
+
+static inline GQuark
+ffmpeg_quark(void)
+{
+ return g_quark_from_static_string("ffmpeg");
+}
+
+static inline bool
+input_ffmpeg_supported(void)
+{
+ void *opaque = nullptr;
+ return avio_enum_protocols(&opaque, 0) != nullptr;
+}
+
+static bool
+input_ffmpeg_init(gcc_unused const config_param &param,
+ G_GNUC_UNUSED GError **error_r)
+{
+ av_register_all();
+
+ /* disable this plugin if there's no registered protocol */
+ if (!input_ffmpeg_supported()) {
+ g_set_error(error_r, ffmpeg_quark(), 0,
+ "No protocol");
+ return false;
+ }
+
+ return true;
+}
+
+static struct input_stream *
+input_ffmpeg_open(const char *uri,
+ Mutex &mutex, Cond &cond,
+ GError **error_r)
+{
+ 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://"))
+ return nullptr;
+
+ AVIOContext *h;
+ int ret = avio_open(&h, uri, AVIO_FLAG_READ);
+ if (ret != 0) {
+ g_set_error(error_r, ffmpeg_quark(), ret,
+ "libavformat failed to open the URI");
+ return nullptr;
+ }
+
+ auto *i = new FfmpegInputStream(uri, mutex, cond, h);
+ return &i->base;
+}
+
+static size_t
+input_ffmpeg_read(struct input_stream *is, void *ptr, size_t size,
+ GError **error_r)
+{
+ FfmpegInputStream *i = (FfmpegInputStream *)is;
+
+ int ret = avio_read(i->h, (unsigned char *)ptr, size);
+ if (ret <= 0) {
+ if (ret < 0)
+ g_set_error(error_r, ffmpeg_quark(), 0,
+ "url_read() failed");
+
+ i->eof = true;
+ return false;
+ }
+
+ is->offset += ret;
+ return (size_t)ret;
+}
+
+static void
+input_ffmpeg_close(struct input_stream *is)
+{
+ FfmpegInputStream *i = (FfmpegInputStream *)is;
+
+ delete i;
+}
+
+static bool
+input_ffmpeg_eof(struct input_stream *is)
+{
+ FfmpegInputStream *i = (FfmpegInputStream *)is;
+
+ return i->eof;
+}
+
+static bool
+input_ffmpeg_seek(struct input_stream *is, goffset offset, int whence,
+ G_GNUC_UNUSED GError **error_r)
+{
+ FfmpegInputStream *i = (FfmpegInputStream *)is;
+ int64_t ret = avio_seek(i->h, offset, whence);
+
+ if (ret >= 0) {
+ i->eof = false;
+ return true;
+ } else {
+ g_set_error(error_r, ffmpeg_quark(), 0, "url_seek() failed");
+ return false;
+ }
+}
+
+const struct input_plugin input_plugin_ffmpeg = {
+ "ffmpeg",
+ input_ffmpeg_init,
+ nullptr,
+ input_ffmpeg_open,
+ input_ffmpeg_close,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ input_ffmpeg_read,
+ input_ffmpeg_eof,
+ input_ffmpeg_seek,
+};
diff --git a/src/input/FfmpegInputPlugin.hxx b/src/input/FfmpegInputPlugin.hxx
new file mode 100644
index 000000000..d5e3a8d9b
--- /dev/null
+++ b/src/input/FfmpegInputPlugin.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_FFMPEG_INPUT_PLUGIN_HXX
+#define MPD_FFMPEG_INPUT_PLUGIN_HXX
+
+/**
+ * An input plugin based on libavformat's "avio" library.
+ */
+extern const struct input_plugin input_plugin_ffmpeg;
+
+#endif
diff --git a/src/input/FileInputPlugin.cxx b/src/input/FileInputPlugin.cxx
new file mode 100644
index 000000000..2eecf32b6
--- /dev/null
+++ b/src/input/FileInputPlugin.cxx
@@ -0,0 +1,164 @@
+/*
+ * 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" /* must be first for large file support */
+#include "FileInputPlugin.hxx"
+#include "InputInternal.hxx"
+#include "InputStream.hxx"
+#include "InputPlugin.hxx"
+#include "fd_util.h"
+#include "open.h"
+#include "io_error.h"
+
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <glib.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "input_file"
+
+struct FileInputStream {
+ struct input_stream base;
+
+ int fd;
+
+ FileInputStream(const char *path, int _fd, off_t size,
+ Mutex &mutex, Cond &cond)
+ :base(input_plugin_file, path, mutex, cond),
+ fd(_fd) {
+ base.size = size;
+ base.seekable = true;
+ base.ready = true;
+ }
+
+ ~FileInputStream() {
+ close(fd);
+ }
+};
+
+static struct input_stream *
+input_file_open(const char *filename,
+ Mutex &mutex, Cond &cond,
+ GError **error_r)
+{
+ int fd, ret;
+ struct stat st;
+
+ if (!g_path_is_absolute(filename))
+ return nullptr;
+
+ fd = open_cloexec(filename, O_RDONLY|O_BINARY, 0);
+ if (fd < 0) {
+ if (errno != ENOENT && errno != ENOTDIR)
+ g_set_error(error_r, errno_quark(), errno,
+ "Failed to open \"%s\": %s",
+ filename, g_strerror(errno));
+ return nullptr;
+ }
+
+ ret = fstat(fd, &st);
+ if (ret < 0) {
+ g_set_error(error_r, errno_quark(), errno,
+ "Failed to stat \"%s\": %s",
+ filename, g_strerror(errno));
+ close(fd);
+ return nullptr;
+ }
+
+ if (!S_ISREG(st.st_mode)) {
+ g_set_error(error_r, errno_quark(), 0,
+ "Not a regular file: %s", filename);
+ close(fd);
+ return nullptr;
+ }
+
+#ifdef POSIX_FADV_SEQUENTIAL
+ posix_fadvise(fd, (off_t)0, st.st_size, POSIX_FADV_SEQUENTIAL);
+#endif
+
+ FileInputStream *fis = new FileInputStream(filename, fd, st.st_size,
+ mutex, cond);
+ return &fis->base;
+}
+
+static bool
+input_file_seek(struct input_stream *is, goffset offset, int whence,
+ GError **error_r)
+{
+ FileInputStream *fis = (FileInputStream *)is;
+
+ offset = (goffset)lseek(fis->fd, (off_t)offset, whence);
+ if (offset < 0) {
+ g_set_error(error_r, errno_quark(), errno,
+ "Failed to seek: %s", g_strerror(errno));
+ return false;
+ }
+
+ is->offset = offset;
+ return true;
+}
+
+static size_t
+input_file_read(struct input_stream *is, void *ptr, size_t size,
+ GError **error_r)
+{
+ FileInputStream *fis = (FileInputStream *)is;
+ ssize_t nbytes;
+
+ nbytes = read(fis->fd, ptr, size);
+ if (nbytes < 0) {
+ g_set_error(error_r, errno_quark(), errno,
+ "Failed to read: %s", g_strerror(errno));
+ return 0;
+ }
+
+ is->offset += nbytes;
+ return (size_t)nbytes;
+}
+
+static void
+input_file_close(struct input_stream *is)
+{
+ FileInputStream *fis = (FileInputStream *)is;
+
+ delete fis;
+}
+
+static bool
+input_file_eof(struct input_stream *is)
+{
+ return is->offset >= is->size;
+}
+
+const struct input_plugin input_plugin_file = {
+ "file",
+ nullptr,
+ nullptr,
+ input_file_open,
+ input_file_close,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ input_file_read,
+ input_file_eof,
+ input_file_seek,
+};
diff --git a/src/input/FileInputPlugin.hxx b/src/input/FileInputPlugin.hxx
new file mode 100644
index 000000000..aacfd0b5d
--- /dev/null
+++ b/src/input/FileInputPlugin.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_FILE_HXX
+#define MPD_INPUT_FILE_HXX
+
+extern const struct input_plugin input_plugin_file;
+
+#endif
diff --git a/src/input/MmsInputPlugin.cxx b/src/input/MmsInputPlugin.cxx
new file mode 100644
index 000000000..b347eb92b
--- /dev/null
+++ b/src/input/MmsInputPlugin.cxx
@@ -0,0 +1,147 @@
+/*
+ * 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.
+ */
+
+#include "config.h"
+#include "MmsInputPlugin.hxx"
+#include "InputInternal.hxx"
+#include "InputStream.hxx"
+#include "InputPlugin.hxx"
+
+#include <glib.h>
+#include <libmms/mmsx.h>
+
+#include <string.h>
+#include <errno.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "input_mms"
+
+struct MmsInputStream {
+ struct input_stream base;
+
+ mmsx_t *mms;
+
+ bool eof;
+
+ MmsInputStream(const char *uri,
+ Mutex &mutex, Cond &cond,
+ mmsx_t *_mms)
+ :base(input_plugin_mms, uri, mutex, cond),
+ mms(_mms), eof(false) {
+ /* XX is this correct? at least this selects the ffmpeg
+ decoder, which seems to work fine*/
+ base.mime = g_strdup("audio/x-ms-wma");
+
+ base.ready = true;
+ }
+
+ ~MmsInputStream() {
+ mmsx_close(mms);
+ }
+};
+
+static inline GQuark
+mms_quark(void)
+{
+ return g_quark_from_static_string("mms");
+}
+
+static struct input_stream *
+input_mms_open(const char *url,
+ Mutex &mutex, Cond &cond,
+ GError **error_r)
+{
+ 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://"))
+ return nullptr;
+
+ const auto mms = mmsx_connect(nullptr, nullptr, url, 128 * 1024);
+ if (mms == nullptr) {
+ g_set_error(error_r, mms_quark(), 0, "mmsx_connect() failed");
+ return nullptr;
+ }
+
+ auto m = new MmsInputStream(url, mutex, cond, mms);
+ return &m->base;
+}
+
+static size_t
+input_mms_read(struct input_stream *is, void *ptr, size_t size,
+ GError **error_r)
+{
+ MmsInputStream *m = (MmsInputStream *)is;
+ int ret;
+
+ ret = mmsx_read(nullptr, m->mms, (char *)ptr, size);
+ if (ret <= 0) {
+ if (ret < 0) {
+ g_set_error(error_r, mms_quark(), errno,
+ "mmsx_read() failed: %s",
+ g_strerror(errno));
+ }
+
+ m->eof = true;
+ return false;
+ }
+
+ is->offset += ret;
+
+ return (size_t)ret;
+}
+
+static void
+input_mms_close(struct input_stream *is)
+{
+ MmsInputStream *m = (MmsInputStream *)is;
+
+ delete m;
+}
+
+static bool
+input_mms_eof(struct input_stream *is)
+{
+ MmsInputStream *m = (MmsInputStream *)is;
+
+ return m->eof;
+}
+
+static bool
+input_mms_seek(G_GNUC_UNUSED struct input_stream *is,
+ G_GNUC_UNUSED goffset offset, G_GNUC_UNUSED int whence,
+ G_GNUC_UNUSED GError **error_r)
+{
+ return false;
+}
+
+const struct input_plugin input_plugin_mms = {
+ "mms",
+ nullptr,
+ nullptr,
+ input_mms_open,
+ input_mms_close,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ input_mms_read,
+ input_mms_eof,
+ input_mms_seek,
+};
diff --git a/src/input/mms_input_plugin.h b/src/input/MmsInputPlugin.hxx
index d6aa593f2..d6aa593f2 100644
--- a/src/input/mms_input_plugin.h
+++ b/src/input/MmsInputPlugin.hxx
diff --git a/src/input/RewindInputPlugin.cxx b/src/input/RewindInputPlugin.cxx
new file mode 100644
index 000000000..d68fd3d73
--- /dev/null
+++ b/src/input/RewindInputPlugin.cxx
@@ -0,0 +1,257 @@
+/*
+ * 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 "RewindInputPlugin.hxx"
+#include "InputInternal.hxx"
+#include "InputStream.hxx"
+#include "InputPlugin.hxx"
+#include "Tag.hxx"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "input_rewind"
+
+extern const struct input_plugin rewind_input_plugin;
+
+struct RewindInputStream {
+ struct input_stream base;
+
+ struct input_stream *input;
+
+ /**
+ * The read position within the buffer. Undefined as long as
+ * ReadingFromBuffer() returns false.
+ */
+ size_t head;
+
+ /**
+ * The write/append position within the buffer.
+ */
+ size_t tail;
+
+ /**
+ * The size of this buffer is the maximum number of bytes
+ * which can be rewinded cheaply without passing the "seek"
+ * call to CURL.
+ *
+ * The origin of this buffer is always the beginning of the
+ * stream (offset 0).
+ */
+ char buffer[64 * 1024];
+
+ RewindInputStream(input_stream *_input)
+ :base(rewind_input_plugin, _input->uri.c_str(),
+ _input->mutex, _input->cond),
+ input(_input), tail(0) {
+ }
+
+ ~RewindInputStream() {
+ input_stream_close(input);
+ }
+
+ /**
+ * Are we currently reading from the buffer, and does the
+ * buffer contain more data for the next read operation?
+ */
+ bool ReadingFromBuffer() const {
+ return tail > 0 && base.offset < input->offset;
+ }
+
+ /**
+ * Copy public attributes from the underlying input stream to the
+ * "rewind" input stream. This function is called when a method of
+ * the underlying stream has returned, which may have modified these
+ * attributes.
+ */
+ void CopyAttributes() {
+ struct input_stream *dest = &base;
+ const struct input_stream *src = input;
+
+ assert(dest != src);
+
+ bool dest_ready = dest->ready;
+
+ dest->ready = src->ready;
+ dest->seekable = src->seekable;
+ dest->size = src->size;
+ dest->offset = src->offset;
+
+ if (!dest_ready && src->ready)
+ dest->mime = src->mime;
+ }
+};
+
+static void
+input_rewind_close(struct input_stream *is)
+{
+ RewindInputStream *r = (RewindInputStream *)is;
+
+ delete r;
+}
+
+static bool
+input_rewind_check(struct input_stream *is, GError **error_r)
+{
+ RewindInputStream *r = (RewindInputStream *)is;
+
+ return input_stream_check(r->input, error_r);
+}
+
+static void
+input_rewind_update(struct input_stream *is)
+{
+ RewindInputStream *r = (RewindInputStream *)is;
+
+ if (!r->ReadingFromBuffer())
+ r->CopyAttributes();
+}
+
+static Tag *
+input_rewind_tag(struct input_stream *is)
+{
+ RewindInputStream *r = (RewindInputStream *)is;
+
+ return input_stream_tag(r->input);
+}
+
+static bool
+input_rewind_available(struct input_stream *is)
+{
+ RewindInputStream *r = (RewindInputStream *)is;
+
+ return input_stream_available(r->input);
+}
+
+static size_t
+input_rewind_read(struct input_stream *is, void *ptr, size_t size,
+ GError **error_r)
+{
+ RewindInputStream *r = (RewindInputStream *)is;
+
+ if (r->ReadingFromBuffer()) {
+ /* buffered read */
+
+ assert(r->head == (size_t)is->offset);
+ assert(r->tail == (size_t)r->input->offset);
+
+ if (size > r->tail - r->head)
+ size = r->tail - r->head;
+
+ memcpy(ptr, r->buffer + r->head, size);
+ r->head += size;
+ is->offset += size;
+
+ return size;
+ } else {
+ /* pass method call to underlying stream */
+
+ size_t nbytes = input_stream_read(r->input, ptr, size, error_r);
+
+ if (r->input->offset > (goffset)sizeof(r->buffer))
+ /* disable buffering */
+ r->tail = 0;
+ else if (r->tail == (size_t)is->offset) {
+ /* append to buffer */
+
+ memcpy(r->buffer + r->tail, ptr, nbytes);
+ r->tail += nbytes;
+
+ assert(r->tail == (size_t)r->input->offset);
+ }
+
+ r->CopyAttributes();
+
+ return nbytes;
+ }
+}
+
+static bool
+input_rewind_eof(struct input_stream *is)
+{
+ RewindInputStream *r = (RewindInputStream *)is;
+
+ return !r->ReadingFromBuffer() && input_stream_eof(r->input);
+}
+
+static bool
+input_rewind_seek(struct input_stream *is, goffset offset, int whence,
+ GError **error_r)
+{
+ RewindInputStream *r = (RewindInputStream *)is;
+
+ assert(is->ready);
+
+ if (whence == SEEK_SET && r->tail > 0 && offset <= (goffset)r->tail) {
+ /* buffered seek */
+
+ assert(!r->ReadingFromBuffer() ||
+ r->head == (size_t)is->offset);
+ assert(r->tail == (size_t)r->input->offset);
+
+ r->head = (size_t)offset;
+ is->offset = offset;
+
+ return true;
+ } else {
+ bool success = input_stream_seek(r->input, offset, whence,
+ error_r);
+ r->CopyAttributes();
+
+ /* disable the buffer, because r->input has left the
+ buffered range now */
+ r->tail = 0;
+
+ return success;
+ }
+}
+
+const struct input_plugin rewind_input_plugin = {
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ input_rewind_close,
+ input_rewind_check,
+ input_rewind_update,
+ input_rewind_tag,
+ input_rewind_available,
+ input_rewind_read,
+ input_rewind_eof,
+ input_rewind_seek,
+};
+
+struct input_stream *
+input_rewind_open(struct input_stream *is)
+{
+ assert(is != NULL);
+ assert(is->offset == 0);
+
+ if (is->seekable)
+ /* seekable resources don't need this plugin */
+ return is;
+
+ RewindInputStream *c = new RewindInputStream(is);
+ return &c->base;
+}
diff --git a/src/input/RewindInputPlugin.hxx b/src/input/RewindInputPlugin.hxx
new file mode 100644
index 000000000..cf21e92f1
--- /dev/null
+++ b/src/input/RewindInputPlugin.hxx
@@ -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.
+ */
+
+/** \file
+ *
+ * A wrapper for an input_stream object which allows cheap buffered
+ * rewinding. This is useful while detecting the stream codec (let
+ * each decoder plugin peek a portion from the stream).
+ */
+
+#ifndef MPD_INPUT_REWIND_HXX
+#define MPD_INPUT_REWIND_HXX
+
+#include "check.h"
+
+struct input_stream;
+
+struct input_stream *
+input_rewind_open(struct input_stream *is);
+
+#endif
diff --git a/src/input/archive_input_plugin.c b/src/input/archive_input_plugin.c
deleted file mode 100644
index 4a038b9e2..000000000
--- a/src/input/archive_input_plugin.c
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "input/archive_input_plugin.h"
-#include "archive_api.h"
-#include "archive_list.h"
-#include "input_plugin.h"
-
-#include <glib.h>
-
-/**
- * select correct archive plugin to handle the input stream
- * may allow stacking of archive plugins. for example for handling
- * tar.gz a gzip handler opens file (through inputfile stream)
- * then it opens a tar handler and sets gzip inputstream as
- * parent_stream so tar plugin fetches file data from gzip
- * plugin and gzip fetches file from disk
- */
-static struct input_stream *
-input_archive_open(const char *pathname,
- GMutex *mutex, GCond *cond,
- GError **error_r)
-{
- const struct archive_plugin *arplug;
- struct archive_file *file;
- char *archive, *filename, *suffix, *pname;
- struct input_stream *is;
-
- if (!g_path_is_absolute(pathname))
- return NULL;
-
- pname = g_strdup(pathname);
- // archive_lookup will modify pname when true is returned
- if (!archive_lookup(pname, &archive, &filename, &suffix)) {
- g_debug("not an archive, lookup %s failed\n", pname);
- g_free(pname);
- return NULL;
- }
-
- //check which archive plugin to use (by ext)
- arplug = archive_plugin_from_suffix(suffix);
- if (!arplug) {
- g_warning("can't handle archive %s\n",archive);
- g_free(pname);
- return NULL;
- }
-
- file = archive_file_open(arplug, archive, error_r);
- if (file == NULL)
- return NULL;
-
- //setup fileops
- is = archive_file_open_stream(file, filename, mutex, cond,
- error_r);
- archive_file_close(file);
- g_free(pname);
-
- return is;
-}
-
-const struct input_plugin input_plugin_archive = {
- .name = "archive",
- .open = input_archive_open,
-};
diff --git a/src/input/archive_input_plugin.h b/src/input/archive_input_plugin.h
deleted file mode 100644
index 51095f37f..000000000
--- a/src/input/archive_input_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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_INPUT_ARCHIVE_H
-#define MPD_INPUT_ARCHIVE_H
-
-extern const struct input_plugin input_plugin_archive;
-
-#endif
diff --git a/src/input/cdio_paranoia_input_plugin.c b/src/input/cdio_paranoia_input_plugin.c
deleted file mode 100644
index 1de7623a1..000000000
--- a/src/input/cdio_paranoia_input_plugin.c
+++ /dev/null
@@ -1,371 +0,0 @@
-/*
- * 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.
- */
-
-/**
- * CD-Audio handling (requires libcdio_paranoia)
- */
-
-#include "config.h"
-#include "input/cdio_paranoia_input_plugin.h"
-#include "input_internal.h"
-#include "input_plugin.h"
-#include "refcount.h"
-
-#include <stdio.h>
-#include <stdint.h>
-#include <stddef.h>
-#include <string.h>
-#include <stdlib.h>
-#include <glib.h>
-#include <assert.h>
-
-#include <cdio/paranoia.h>
-#include <cdio/cd_types.h>
-
-struct input_cdio_paranoia {
- struct input_stream base;
-
- cdrom_drive_t *drv;
- CdIo_t *cdio;
- cdrom_paranoia_t *para;
-
- lsn_t lsn_from, lsn_to;
- int lsn_relofs;
-
- int trackno;
-
- char buffer[CDIO_CD_FRAMESIZE_RAW];
- int buffer_lsn;
-};
-
-static inline GQuark
-cdio_quark(void)
-{
- return g_quark_from_static_string("cdio");
-}
-
-static void
-input_cdio_close(struct input_stream *is)
-{
- struct input_cdio_paranoia *i = (struct input_cdio_paranoia *)is;
-
- if (i->para)
- cdio_paranoia_free(i->para);
- if (i->drv)
- cdio_cddap_close_no_free_cdio( i->drv);
- if (i->cdio)
- cdio_destroy( i->cdio );
-
- input_stream_deinit(&i->base);
- g_free(i);
-}
-
-struct cdio_uri {
- char device[64];
- int track;
-};
-
-static bool
-parse_cdio_uri(struct cdio_uri *dest, const char *src, GError **error_r)
-{
- if (!g_str_has_prefix(src, "cdda://"))
- return false;
-
- src += 7;
-
- if (*src == 0) {
- /* play the whole CD in the default drive */
- dest->device[0] = 0;
- dest->track = -1;
- return true;
- }
-
- const char *slash = strrchr(src, '/');
- if (slash == NULL) {
- /* play the whole CD in the specified drive */
- g_strlcpy(dest->device, src, sizeof(dest->device));
- dest->track = -1;
- return true;
- }
-
- size_t device_length = slash - src;
- if (device_length >= sizeof(dest->device))
- device_length = sizeof(dest->device) - 1;
-
- memcpy(dest->device, src, device_length);
- dest->device[device_length] = 0;
-
- const char *track = slash + 1;
-
- char *endptr;
- dest->track = strtoul(track, &endptr, 10);
- if (*endptr != 0) {
- g_set_error(error_r, cdio_quark(), 0,
- "Malformed track number");
- return false;
- }
-
- if (endptr == track)
- /* play the whole CD */
- dest->track = -1;
-
- return true;
-}
-
-static char *
-cdio_detect_device(void)
-{
- char **devices = cdio_get_devices_with_cap(NULL, CDIO_FS_AUDIO, false);
- if (devices == NULL)
- return NULL;
-
- char *device = g_strdup(devices[0]);
- cdio_free_device_list(devices);
-
- return device;
-}
-
-static struct input_stream *
-input_cdio_open(const char *uri,
- GMutex *mutex, GCond *cond,
- GError **error_r)
-{
- struct input_cdio_paranoia *i;
-
- struct cdio_uri parsed_uri;
- if (!parse_cdio_uri(&parsed_uri, uri, error_r))
- return NULL;
-
- i = g_new(struct input_cdio_paranoia, 1);
- input_stream_init(&i->base, &input_plugin_cdio_paranoia, uri,
- mutex, cond);
-
- /* initialize everything (should be already) */
- i->drv = NULL;
- i->cdio = NULL;
- i->para = NULL;
- i->trackno = parsed_uri.track;
-
- /* get list of CD's supporting CD-DA */
- char *device = parsed_uri.device[0] != 0
- ? g_strdup(parsed_uri.device)
- : cdio_detect_device();
- if (device == NULL) {
- g_set_error(error_r, cdio_quark(), 0,
- "Unable find or access a CD-ROM drive with an audio CD in it.");
- input_cdio_close(&i->base);
- return NULL;
- }
-
- /* Found such a CD-ROM with a CD-DA loaded. Use the first drive in the list. */
- i->cdio = cdio_open(device, DRIVER_UNKNOWN);
- g_free(device);
-
- i->drv = cdio_cddap_identify_cdio(i->cdio, 1, NULL);
-
- if ( !i->drv ) {
- g_set_error(error_r, cdio_quark(), 0,
- "Unable to identify audio CD disc.");
- input_cdio_close(&i->base);
- return NULL;
- }
-
- cdda_verbose_set(i->drv, CDDA_MESSAGE_FORGETIT, CDDA_MESSAGE_FORGETIT);
-
- if ( 0 != cdio_cddap_open(i->drv) ) {
- g_set_error(error_r, cdio_quark(), 0, "Unable to open disc.");
- input_cdio_close(&i->base);
- return NULL;
- }
-
- bool reverse_endian;
- switch (data_bigendianp(i->drv)) {
- case -1:
- g_debug("cdda: drive returns unknown audio data");
- reverse_endian = false;
- break;
- case 0:
- g_debug("cdda: drive returns audio data Little Endian.");
- reverse_endian = G_BYTE_ORDER == G_BIG_ENDIAN;
- break;
- case 1:
- g_debug("cdda: drive returns audio data Big Endian.");
- reverse_endian = G_BYTE_ORDER == G_LITTLE_ENDIAN;
- break;
- default:
- g_set_error(error_r, cdio_quark(), 0,
- "Drive returns unknown data type %d",
- data_bigendianp(i->drv));
- input_cdio_close(&i->base);
- return NULL;
- }
-
- i->lsn_relofs = 0;
-
- if (i->trackno >= 0) {
- i->lsn_from = cdio_get_track_lsn(i->cdio, i->trackno);
- i->lsn_to = cdio_get_track_last_lsn(i->cdio, i->trackno);
- } else {
- i->lsn_from = 0;
- i->lsn_to = cdio_get_disc_last_lsn(i->cdio);
- }
-
- i->para = cdio_paranoia_init(i->drv);
-
- /* Set reading mode for full paranoia, but allow skipping sectors. */
- paranoia_modeset(i->para, PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP);
-
- /* seek to beginning of the track */
- cdio_paranoia_seek(i->para, i->lsn_from, SEEK_SET);
-
- i->base.ready = true;
- i->base.seekable = true;
- i->base.size = (i->lsn_to - i->lsn_from + 1) * CDIO_CD_FRAMESIZE_RAW;
-
- /* hack to make MPD select the "pcm" decoder plugin */
- i->base.mime = g_strdup(reverse_endian
- ? "audio/x-mpd-cdda-pcm-reverse"
- : "audio/x-mpd-cdda-pcm");
-
- return &i->base;
-}
-
-static bool
-input_cdio_seek(struct input_stream *is,
- goffset offset, int whence, GError **error_r)
-{
- struct input_cdio_paranoia *cis = (struct input_cdio_paranoia *)is;
-
- /* calculate absolute offset */
- switch (whence) {
- case SEEK_SET:
- break;
- case SEEK_CUR:
- offset += cis->base.offset;
- break;
- case SEEK_END:
- offset += cis->base.size;
- break;
- }
-
- if (offset < 0 || offset > cis->base.size) {
- g_set_error(error_r, cdio_quark(), 0,
- "Invalid offset to seek %ld (%ld)",
- (long int)offset, (long int)cis->base.size);
- return false;
- }
-
- /* simple case */
- if (offset == cis->base.offset)
- return true;
-
- /* calculate current LSN */
- cis->lsn_relofs = offset / CDIO_CD_FRAMESIZE_RAW;
- cis->base.offset = offset;
-
- cdio_paranoia_seek(cis->para, cis->lsn_from + cis->lsn_relofs, SEEK_SET);
-
- return true;
-}
-
-static size_t
-input_cdio_read(struct input_stream *is, void *ptr, size_t length,
- GError **error_r)
-{
- struct input_cdio_paranoia *cis = (struct input_cdio_paranoia *)is;
- size_t nbytes = 0;
- int diff;
- size_t len, maxwrite;
- int16_t *rbuf;
- char *s_err, *s_mess;
- char *wptr = (char *) ptr;
-
- while (length > 0) {
-
-
- /* end of track ? */
- if (cis->lsn_from + cis->lsn_relofs > cis->lsn_to)
- break;
-
- //current sector was changed ?
- if (cis->lsn_relofs != cis->buffer_lsn) {
- rbuf = cdio_paranoia_read(cis->para, NULL);
-
- s_err = cdda_errors(cis->drv);
- if (s_err) {
- g_warning("paranoia_read: %s", s_err );
- free(s_err);
- }
- s_mess = cdda_messages(cis->drv);
- if (s_mess) {
- free(s_mess);
- }
- if (!rbuf) {
- g_set_error(error_r, cdio_quark(), 0,
- "paranoia read error. Stopping.");
- return 0;
- }
- //store current buffer
- memcpy(cis->buffer, rbuf, CDIO_CD_FRAMESIZE_RAW);
- cis->buffer_lsn = cis->lsn_relofs;
- } else {
- //use cached sector
- rbuf = (int16_t*) cis->buffer;
- }
-
- //correct offset
- diff = cis->base.offset - cis->lsn_relofs * CDIO_CD_FRAMESIZE_RAW;
-
- assert(diff >= 0 && diff < CDIO_CD_FRAMESIZE_RAW);
-
- maxwrite = CDIO_CD_FRAMESIZE_RAW - diff; //# of bytes pending in current buffer
- len = (length < maxwrite? length : maxwrite);
-
- //skip diff bytes from this lsn
- memcpy(wptr, ((char*)rbuf) + diff, len);
- //update pointer
- wptr += len;
- nbytes += len;
-
- //update offset
- cis->base.offset += len;
- cis->lsn_relofs = cis->base.offset / CDIO_CD_FRAMESIZE_RAW;
- //update length
- length -= len;
- }
-
- return nbytes;
-}
-
-static bool
-input_cdio_eof(struct input_stream *is)
-{
- struct input_cdio_paranoia *cis = (struct input_cdio_paranoia *)is;
-
- return (cis->lsn_from + cis->lsn_relofs > cis->lsn_to);
-}
-
-const struct input_plugin input_plugin_cdio_paranoia = {
- .name = "cdio_paranoia",
- .open = input_cdio_open,
- .close = input_cdio_close,
- .seek = input_cdio_seek,
- .read = input_cdio_read,
- .eof = input_cdio_eof
-};
diff --git a/src/input/cdio_paranoia_input_plugin.h b/src/input/cdio_paranoia_input_plugin.h
deleted file mode 100644
index 71c5cbe8d..000000000
--- a/src/input/cdio_paranoia_input_plugin.h
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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_CDIO_PARANOIA_INPUT_PLUGIN_H
-#define MPD_CDIO_PARANOIA_INPUT_PLUGIN_H
-
-/**
- * An input plugin based on libcdio_paranoia library.
- */
-extern const struct input_plugin input_plugin_cdio_paranoia;
-
-#endif
diff --git a/src/input/curl_input_plugin.c b/src/input/curl_input_plugin.c
deleted file mode 100644
index 3f191141e..000000000
--- a/src/input/curl_input_plugin.c
+++ /dev/null
@@ -1,1301 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "input/curl_input_plugin.h"
-#include "input_internal.h"
-#include "input_plugin.h"
-#include "conf.h"
-#include "tag.h"
-#include "icy_metadata.h"
-#include "io_thread.h"
-#include "glib_compat.h"
-
-#include <assert.h>
-
-#if defined(WIN32)
- #include <winsock2.h>
-#else
- #include <sys/select.h>
-#endif
-
-#include <string.h>
-#include <errno.h>
-
-#include <curl/curl.h>
-#include <glib.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "input_curl"
-
-/**
- * Do not buffer more than this number of bytes. It should be a
- * reasonable limit that doesn't make low-end machines suffer too
- * much, but doesn't cause stuttering on high-latency lines.
- */
-static const size_t CURL_MAX_BUFFERED = 512 * 1024;
-
-/**
- * Resume the stream at this number of bytes after it has been paused.
- */
-static const size_t CURL_RESUME_AT = 384 * 1024;
-
-/**
- * Buffers created by input_curl_writefunction().
- */
-struct buffer {
- /** size of the payload */
- size_t size;
-
- /** how much has been consumed yet? */
- size_t consumed;
-
- /** the payload */
- unsigned char data[sizeof(long)];
-};
-
-struct input_curl {
- struct input_stream base;
-
- /* some buffers which were passed to libcurl, which we have
- too free */
- char *url, *range;
- struct curl_slist *request_headers;
-
- /** the curl handles */
- CURL *easy;
-
- /** the GMainLoop source used to poll all CURL file
- descriptors */
- GSource *source;
-
- /** the source id of #source */
- guint source_id;
-
- /** a linked list of all registered GPollFD objects */
- GSList *fds;
-
- /** list of buffers, where input_curl_writefunction() appends
- to, and input_curl_read() reads from them */
- GQueue *buffers;
-
-#if LIBCURL_VERSION_NUM >= 0x071200
- /**
- * Is the connection currently paused? That happens when the
- * buffer was getting too large. It will be unpaused when the
- * buffer is below the threshold again.
- */
- bool paused;
-#endif
-
- /** error message provided by libcurl */
- char error[CURL_ERROR_SIZE];
-
- /** parser for icy-metadata */
- struct icy_metadata icy_metadata;
-
- /** the stream name from the icy-name response header */
- char *meta_name;
-
- /** the tag object ready to be requested via
- input_stream_tag() */
- struct tag *tag;
-
- GError *postponed_error;
-};
-
-/** libcurl should accept "ICY 200 OK" */
-static struct curl_slist *http_200_aliases;
-
-/** HTTP proxy settings */
-static const char *proxy, *proxy_user, *proxy_password;
-static unsigned proxy_port;
-
-static struct {
- CURLM *multi;
-
- /**
- * A linked list of all active HTTP requests. An active
- * request is one that doesn't have the "eof" flag set.
- */
- GSList *requests;
-
- /**
- * The GMainLoop source used to poll all CURL file
- * descriptors.
- */
- GSource *source;
-
- /**
- * The source id of #source.
- */
- guint source_id;
-
- GSList *fds;
-
-#if LIBCURL_VERSION_NUM >= 0x070f04
- /**
- * Did CURL give us a timeout? If yes, then we need to call
- * curl_multi_perform(), even if there was no event on any
- * file descriptor.
- */
- bool timeout;
-
- /**
- * The absolute time stamp when the timeout expires. This is
- * used in the GSource method check().
- */
- gint64 absolute_timeout;
-#endif
-} curl;
-
-static inline GQuark
-curl_quark(void)
-{
- return g_quark_from_static_string("curl");
-}
-
-/**
- * Find a request by its CURL "easy" handle.
- *
- * Runs in the I/O thread. No lock needed.
- */
-static struct input_curl *
-input_curl_find_request(CURL *easy)
-{
- assert(io_thread_inside());
-
- for (GSList *i = curl.requests; i != NULL; i = g_slist_next(i)) {
- struct input_curl *c = i->data;
- if (c->easy == easy)
- return c;
- }
-
- return NULL;
-}
-
-#if LIBCURL_VERSION_NUM >= 0x071200
-
-static gpointer
-input_curl_resume(gpointer data)
-{
- assert(io_thread_inside());
-
- struct input_curl *c = data;
-
- if (c->paused) {
- c->paused = false;
- curl_easy_pause(c->easy, CURLPAUSE_CONT);
- }
-
- return NULL;
-}
-
-#endif
-
-/**
- * Calculates the GLib event bit mask for one file descriptor,
- * obtained from three #fd_set objects filled by curl_multi_fdset().
- */
-static gushort
-input_curl_fd_events(int fd, fd_set *rfds, fd_set *wfds, fd_set *efds)
-{
- gushort events = 0;
-
- if (FD_ISSET(fd, rfds)) {
- events |= G_IO_IN | G_IO_HUP | G_IO_ERR;
- FD_CLR(fd, rfds);
- }
-
- if (FD_ISSET(fd, wfds)) {
- events |= G_IO_OUT | G_IO_ERR;
- FD_CLR(fd, wfds);
- }
-
- if (FD_ISSET(fd, efds)) {
- events |= G_IO_HUP | G_IO_ERR;
- FD_CLR(fd, efds);
- }
-
- return events;
-}
-
-/**
- * Updates all registered GPollFD objects, unregisters old ones,
- * registers new ones.
- *
- * Runs in the I/O thread. No lock needed.
- */
-static void
-curl_update_fds(void)
-{
- assert(io_thread_inside());
-
- fd_set rfds, wfds, efds;
-
- FD_ZERO(&rfds);
- FD_ZERO(&wfds);
- FD_ZERO(&efds);
-
- int max_fd;
- CURLMcode mcode = curl_multi_fdset(curl.multi, &rfds, &wfds,
- &efds, &max_fd);
- if (mcode != CURLM_OK) {
- g_warning("curl_multi_fdset() failed: %s\n",
- curl_multi_strerror(mcode));
- return;
- }
-
- GSList *fds = curl.fds;
- curl.fds = NULL;
-
- while (fds != NULL) {
- GPollFD *poll_fd = fds->data;
- gushort events = input_curl_fd_events(poll_fd->fd, &rfds,
- &wfds, &efds);
-
- assert(poll_fd->events != 0);
-
- fds = g_slist_remove(fds, poll_fd);
-
- if (events != poll_fd->events)
- g_source_remove_poll(curl.source, poll_fd);
-
- if (events != 0) {
- if (events != poll_fd->events) {
- poll_fd->events = events;
- g_source_add_poll(curl.source, poll_fd);
- }
-
- curl.fds = g_slist_prepend(curl.fds, poll_fd);
- } else {
- g_free(poll_fd);
- }
- }
-
- for (int fd = 0; fd <= max_fd; ++fd) {
- gushort events = input_curl_fd_events(fd, &rfds, &wfds, &efds);
- if (events != 0) {
- GPollFD *poll_fd = g_new(GPollFD, 1);
- poll_fd->fd = fd;
- poll_fd->events = events;
- g_source_add_poll(curl.source, poll_fd);
- curl.fds = g_slist_prepend(curl.fds, poll_fd);
- }
- }
-}
-
-/**
- * Runs in the I/O thread. No lock needed.
- */
-static bool
-input_curl_easy_add(struct input_curl *c, GError **error_r)
-{
- assert(io_thread_inside());
- assert(c != NULL);
- assert(c->easy != NULL);
- assert(input_curl_find_request(c->easy) == NULL);
-
- curl.requests = g_slist_prepend(curl.requests, c);
-
- CURLMcode mcode = curl_multi_add_handle(curl.multi, c->easy);
- if (mcode != CURLM_OK) {
- g_set_error(error_r, curl_quark(), mcode,
- "curl_multi_add_handle() failed: %s",
- curl_multi_strerror(mcode));
- return false;
- }
-
- curl_update_fds();
-
- return true;
-}
-
-struct easy_add_params {
- struct input_curl *c;
- GError **error_r;
-};
-
-static gpointer
-input_curl_easy_add_callback(gpointer data)
-{
- const struct easy_add_params *params = data;
-
- bool success = input_curl_easy_add(params->c, params->error_r);
- return GUINT_TO_POINTER(success);
-}
-
-/**
- * Call input_curl_easy_add() in the I/O thread. May be called from
- * any thread. Caller must not hold a mutex.
- */
-static bool
-input_curl_easy_add_indirect(struct input_curl *c, GError **error_r)
-{
- assert(c != NULL);
- assert(c->easy != NULL);
-
- struct easy_add_params params = {
- .c = c,
- .error_r = error_r,
- };
-
- gpointer result =
- io_thread_call(input_curl_easy_add_callback, &params);
- return GPOINTER_TO_UINT(result);
-}
-
-/**
- * Frees the current "libcurl easy" handle, and everything associated
- * with it.
- *
- * Runs in the I/O thread.
- */
-static void
-input_curl_easy_free(struct input_curl *c)
-{
- assert(io_thread_inside());
- assert(c != NULL);
-
- if (c->easy == NULL)
- return;
-
- curl.requests = g_slist_remove(curl.requests, c);
-
- curl_multi_remove_handle(curl.multi, c->easy);
- curl_easy_cleanup(c->easy);
- c->easy = NULL;
-
- curl_slist_free_all(c->request_headers);
- c->request_headers = NULL;
-
- g_free(c->range);
- c->range = NULL;
-}
-
-static gpointer
-input_curl_easy_free_callback(gpointer data)
-{
- struct input_curl *c = data;
-
- input_curl_easy_free(c);
- curl_update_fds();
-
- return NULL;
-}
-
-/**
- * Frees the current "libcurl easy" handle, and everything associated
- * with it.
- *
- * The mutex must not be locked.
- */
-static void
-input_curl_easy_free_indirect(struct input_curl *c)
-{
- io_thread_call(input_curl_easy_free_callback, c);
- assert(c->easy == NULL);
-}
-
-/**
- * Abort and free all HTTP requests.
- *
- * Runs in the I/O thread. The caller must not hold locks.
- */
-static void
-input_curl_abort_all_requests(GError *error)
-{
- assert(io_thread_inside());
- assert(error != NULL);
-
- while (curl.requests != NULL) {
- struct input_curl *c = curl.requests->data;
- assert(c->postponed_error == NULL);
-
- input_curl_easy_free(c);
-
- g_mutex_lock(c->base.mutex);
- c->postponed_error = g_error_copy(error);
- c->base.ready = true;
- g_cond_broadcast(c->base.cond);
- g_mutex_unlock(c->base.mutex);
- }
-
- g_error_free(error);
-
-}
-
-/**
- * A HTTP request is finished.
- *
- * Runs in the I/O thread. The caller must not hold locks.
- */
-static void
-input_curl_request_done(struct input_curl *c, CURLcode result, long status)
-{
- assert(io_thread_inside());
- assert(c != NULL);
- assert(c->easy == NULL);
- assert(c->postponed_error == NULL);
-
- g_mutex_lock(c->base.mutex);
-
- if (result != CURLE_OK) {
- c->postponed_error = g_error_new(curl_quark(), result,
- "curl failed: %s",
- c->error);
- } else if (status < 200 || status >= 300) {
- c->postponed_error = g_error_new(curl_quark(), 0,
- "got HTTP status %ld",
- status);
- }
-
- c->base.ready = true;
- g_cond_broadcast(c->base.cond);
- g_mutex_unlock(c->base.mutex);
-}
-
-static void
-input_curl_handle_done(CURL *easy_handle, CURLcode result)
-{
- struct input_curl *c = input_curl_find_request(easy_handle);
- assert(c != NULL);
-
- long status = 0;
- curl_easy_getinfo(easy_handle, CURLINFO_RESPONSE_CODE, &status);
-
- input_curl_easy_free(c);
- input_curl_request_done(c, result, status);
-}
-
-/**
- * Check for finished HTTP responses.
- *
- * Runs in the I/O thread. The caller must not hold locks.
- */
-static void
-input_curl_info_read(void)
-{
- assert(io_thread_inside());
-
- CURLMsg *msg;
- int msgs_in_queue;
-
- while ((msg = curl_multi_info_read(curl.multi,
- &msgs_in_queue)) != NULL) {
- if (msg->msg == CURLMSG_DONE)
- input_curl_handle_done(msg->easy_handle, msg->data.result);
- }
-}
-
-/**
- * Give control to CURL.
- *
- * Runs in the I/O thread. The caller must not hold locks.
- */
-static bool
-input_curl_perform(void)
-{
- assert(io_thread_inside());
-
- CURLMcode mcode;
-
- do {
- int running_handles;
- mcode = curl_multi_perform(curl.multi, &running_handles);
- } while (mcode == CURLM_CALL_MULTI_PERFORM);
-
- if (mcode != CURLM_OK && mcode != CURLM_CALL_MULTI_PERFORM) {
- GError *error = g_error_new(curl_quark(), mcode,
- "curl_multi_perform() failed: %s",
- curl_multi_strerror(mcode));
- input_curl_abort_all_requests(error);
- return false;
- }
-
- return true;
-}
-
-/*
- * GSource methods
- *
- */
-
-/**
- * The GSource prepare() method implementation.
- */
-static gboolean
-input_curl_source_prepare(G_GNUC_UNUSED GSource *source, gint *timeout_r)
-{
- curl_update_fds();
-
-#if LIBCURL_VERSION_NUM >= 0x070f04
- curl.timeout = false;
-
- long timeout2;
- CURLMcode mcode = curl_multi_timeout(curl.multi, &timeout2);
- if (mcode == CURLM_OK) {
- if (timeout2 >= 0)
- curl.absolute_timeout = g_source_get_time(source)
- + timeout2 * 1000;
-
- if (timeout2 >= 0 && timeout2 < 10)
- /* CURL 7.21.1 likes to report "timeout=0",
- which means we're running in a busy loop.
- Quite a bad idea to waste so much CPU.
- Let's use a lower limit of 10ms. */
- timeout2 = 10;
-
- *timeout_r = timeout2;
-
- curl.timeout = timeout2 >= 0;
- } else
- g_warning("curl_multi_timeout() failed: %s\n",
- curl_multi_strerror(mcode));
-#else
- (void)timeout_r;
-#endif
-
- return false;
-}
-
-/**
- * The GSource check() method implementation.
- */
-static gboolean
-input_curl_source_check(G_GNUC_UNUSED GSource *source)
-{
-#if LIBCURL_VERSION_NUM >= 0x070f04
- if (curl.timeout) {
- /* when a timeout has expired, we need to call
- curl_multi_perform(), even if there was no file
- descriptor event */
-
- if (g_source_get_time(source) >= curl.absolute_timeout)
- return true;
- }
-#endif
-
- for (GSList *i = curl.fds; i != NULL; i = i->next) {
- GPollFD *poll_fd = i->data;
- if (poll_fd->revents != 0)
- return true;
- }
-
- return false;
-}
-
-/**
- * The GSource dispatch() method implementation. The callback isn't
- * used, because we're handling all events directly.
- */
-static gboolean
-input_curl_source_dispatch(G_GNUC_UNUSED GSource *source,
- G_GNUC_UNUSED GSourceFunc callback,
- G_GNUC_UNUSED gpointer user_data)
-{
- if (input_curl_perform())
- input_curl_info_read();
-
- 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 curl_source_funcs = {
- .prepare = input_curl_source_prepare,
- .check = input_curl_source_check,
- .dispatch = input_curl_source_dispatch,
-};
-
-/*
- * input_plugin methods
- *
- */
-
-static bool
-input_curl_init(const struct config_param *param,
- G_GNUC_UNUSED GError **error_r)
-{
- CURLcode code = curl_global_init(CURL_GLOBAL_ALL);
- if (code != CURLE_OK) {
- g_set_error(error_r, curl_quark(), code,
- "curl_global_init() failed: %s\n",
- curl_easy_strerror(code));
- return false;
- }
-
- http_200_aliases = curl_slist_append(http_200_aliases, "ICY 200 OK");
-
- proxy = config_get_block_string(param, "proxy", NULL);
- proxy_port = config_get_block_unsigned(param, "proxy_port", 0);
- proxy_user = config_get_block_string(param, "proxy_user", NULL);
- proxy_password = config_get_block_string(param, "proxy_password",
- NULL);
-
- if (proxy == NULL) {
- /* deprecated proxy configuration */
- proxy = config_get_string(CONF_HTTP_PROXY_HOST, NULL);
- proxy_port = config_get_positive(CONF_HTTP_PROXY_PORT, 0);
- proxy_user = config_get_string(CONF_HTTP_PROXY_USER, NULL);
- proxy_password = config_get_string(CONF_HTTP_PROXY_PASSWORD,
- "");
- }
-
- curl.multi = curl_multi_init();
- if (curl.multi == NULL) {
- g_set_error(error_r, curl_quark(), 0,
- "curl_multi_init() failed");
- return false;
- }
-
- curl.source = g_source_new(&curl_source_funcs, sizeof(*curl.source));
- curl.source_id = g_source_attach(curl.source, io_thread_context());
-
- return true;
-}
-
-static gpointer
-curl_destroy_sources(G_GNUC_UNUSED gpointer data)
-{
- g_source_destroy(curl.source);
-
- return NULL;
-}
-
-static void
-input_curl_finish(void)
-{
- assert(curl.requests == NULL);
-
- io_thread_call(curl_destroy_sources, NULL);
-
- curl_multi_cleanup(curl.multi);
-
- curl_slist_free_all(http_200_aliases);
-
- curl_global_cleanup();
-}
-
-#if LIBCURL_VERSION_NUM >= 0x071200
-
-/**
- * Determine the total sizes of all buffers, including portions that
- * have already been consumed.
- *
- * The caller must lock the mutex.
- */
-G_GNUC_PURE
-static size_t
-curl_total_buffer_size(const struct input_curl *c)
-{
- size_t total = 0;
-
- for (GList *i = g_queue_peek_head_link(c->buffers);
- i != NULL; i = g_list_next(i)) {
- struct buffer *buffer = i->data;
- total += buffer->size;
- }
-
- return total;
-}
-
-#endif
-
-static void
-buffer_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
-{
- struct buffer *buffer = data;
-
- assert(buffer->consumed <= buffer->size);
-
- g_free(buffer);
-}
-
-static void
-input_curl_flush_buffers(struct input_curl *c)
-{
- g_queue_foreach(c->buffers, buffer_free_callback, NULL);
- g_queue_clear(c->buffers);
-}
-
-/**
- * Frees this stream, including the input_stream struct.
- */
-static void
-input_curl_free(struct input_curl *c)
-{
- if (c->tag != NULL)
- tag_free(c->tag);
- g_free(c->meta_name);
-
- input_curl_easy_free_indirect(c);
- input_curl_flush_buffers(c);
-
- g_queue_free(c->buffers);
-
- if (c->postponed_error != NULL)
- g_error_free(c->postponed_error);
-
- g_free(c->url);
- input_stream_deinit(&c->base);
- g_free(c);
-}
-
-static bool
-input_curl_check(struct input_stream *is, GError **error_r)
-{
- struct input_curl *c = (struct input_curl *)is;
-
- bool success = c->postponed_error == NULL;
- if (!success) {
- g_propagate_error(error_r, c->postponed_error);
- c->postponed_error = NULL;
- }
-
- return success;
-}
-
-static struct tag *
-input_curl_tag(struct input_stream *is)
-{
- struct input_curl *c = (struct input_curl *)is;
- struct tag *tag = c->tag;
-
- c->tag = NULL;
- return tag;
-}
-
-static bool
-fill_buffer(struct input_curl *c, GError **error_r)
-{
- while (c->easy != NULL && g_queue_is_empty(c->buffers))
- g_cond_wait(c->base.cond, c->base.mutex);
-
- if (c->postponed_error != NULL) {
- g_propagate_error(error_r, c->postponed_error);
- c->postponed_error = NULL;
- return false;
- }
-
- return !g_queue_is_empty(c->buffers);
-}
-
-/**
- * Mark a part of the buffer object as consumed.
- */
-static struct buffer *
-consume_buffer(struct buffer *buffer, size_t length)
-{
- assert(buffer != NULL);
- assert(buffer->consumed < buffer->size);
-
- buffer->consumed += length;
- if (buffer->consumed < buffer->size)
- return buffer;
-
- assert(buffer->consumed == buffer->size);
-
- g_free(buffer);
-
- return NULL;
-}
-
-static size_t
-read_from_buffer(struct icy_metadata *icy_metadata, GQueue *buffers,
- void *dest0, size_t length)
-{
- struct buffer *buffer = g_queue_pop_head(buffers);
- uint8_t *dest = dest0;
- size_t nbytes = 0;
-
- assert(buffer->size > 0);
- assert(buffer->consumed < buffer->size);
-
- if (length > buffer->size - buffer->consumed)
- length = buffer->size - buffer->consumed;
-
- while (true) {
- size_t chunk;
-
- chunk = icy_data(icy_metadata, length);
- if (chunk > 0) {
- memcpy(dest, buffer->data + buffer->consumed,
- chunk);
- buffer = consume_buffer(buffer, chunk);
-
- nbytes += chunk;
- dest += chunk;
- length -= chunk;
-
- if (length == 0)
- break;
-
- assert(buffer != NULL);
- }
-
- chunk = icy_meta(icy_metadata, buffer->data + buffer->consumed,
- length);
- if (chunk > 0) {
- buffer = consume_buffer(buffer, chunk);
-
- length -= chunk;
-
- if (length == 0)
- break;
-
- assert(buffer != NULL);
- }
- }
-
- if (buffer != NULL)
- g_queue_push_head(buffers, buffer);
-
- return nbytes;
-}
-
-static void
-copy_icy_tag(struct input_curl *c)
-{
- struct tag *tag = icy_tag(&c->icy_metadata);
-
- if (tag == NULL)
- return;
-
- if (c->tag != NULL)
- tag_free(c->tag);
-
- if (c->meta_name != NULL && !tag_has_type(tag, TAG_NAME))
- tag_add_item(tag, TAG_NAME, c->meta_name);
-
- c->tag = tag;
-}
-
-static bool
-input_curl_available(struct input_stream *is)
-{
- struct input_curl *c = (struct input_curl *)is;
-
- return c->postponed_error != NULL || c->easy == NULL ||
- !g_queue_is_empty(c->buffers);
-}
-
-static size_t
-input_curl_read(struct input_stream *is, void *ptr, size_t size,
- GError **error_r)
-{
- struct input_curl *c = (struct input_curl *)is;
- bool success;
- size_t nbytes = 0;
- char *dest = ptr;
-
- do {
- /* fill the buffer */
-
- success = fill_buffer(c, error_r);
- if (!success)
- return 0;
-
- /* send buffer contents */
-
- while (size > 0 && !g_queue_is_empty(c->buffers)) {
- size_t copy = read_from_buffer(&c->icy_metadata, c->buffers,
- dest + nbytes, size);
-
- nbytes += copy;
- size -= copy;
- }
- } while (nbytes == 0);
-
- if (icy_defined(&c->icy_metadata))
- copy_icy_tag(c);
-
- is->offset += (goffset)nbytes;
-
-#if LIBCURL_VERSION_NUM >= 0x071200
- if (c->paused && curl_total_buffer_size(c) < CURL_RESUME_AT) {
- g_mutex_unlock(c->base.mutex);
- io_thread_call(input_curl_resume, c);
- g_mutex_lock(c->base.mutex);
- }
-#endif
-
- return nbytes;
-}
-
-static void
-input_curl_close(struct input_stream *is)
-{
- struct input_curl *c = (struct input_curl *)is;
-
- input_curl_free(c);
-}
-
-static bool
-input_curl_eof(G_GNUC_UNUSED struct input_stream *is)
-{
- struct input_curl *c = (struct input_curl *)is;
-
- return c->easy == NULL && g_queue_is_empty(c->buffers);
-}
-
-/** called by curl when new data is available */
-static size_t
-input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream)
-{
- struct input_curl *c = (struct input_curl *)stream;
- const char *header = ptr, *end, *value;
- char name[64];
-
- size *= nmemb;
- end = header + size;
-
- value = memchr(header, ':', size);
- if (value == NULL || (size_t)(value - header) >= sizeof(name))
- return size;
-
- memcpy(name, header, value - header);
- name[value - header] = 0;
-
- /* skip the colon */
-
- ++value;
-
- /* strip the value */
-
- while (value < end && g_ascii_isspace(*value))
- ++value;
-
- while (end > value && g_ascii_isspace(end[-1]))
- --end;
-
- if (g_ascii_strcasecmp(name, "accept-ranges") == 0) {
- /* a stream with icy-metadata is not seekable */
- if (!icy_defined(&c->icy_metadata))
- c->base.seekable = true;
- } else if (g_ascii_strcasecmp(name, "content-length") == 0) {
- char buffer[64];
-
- if ((size_t)(end - header) >= sizeof(buffer))
- return size;
-
- memcpy(buffer, value, end - value);
- buffer[end - value] = 0;
-
- c->base.size = c->base.offset + g_ascii_strtoull(buffer, NULL, 10);
- } else if (g_ascii_strcasecmp(name, "content-type") == 0) {
- g_free(c->base.mime);
- c->base.mime = g_strndup(value, end - value);
- } else if (g_ascii_strcasecmp(name, "icy-name") == 0 ||
- g_ascii_strcasecmp(name, "ice-name") == 0 ||
- g_ascii_strcasecmp(name, "x-audiocast-name") == 0) {
- g_free(c->meta_name);
- c->meta_name = g_strndup(value, end - value);
-
- if (c->tag != NULL)
- tag_free(c->tag);
-
- c->tag = tag_new();
- tag_add_item(c->tag, TAG_NAME, c->meta_name);
- } else if (g_ascii_strcasecmp(name, "icy-metaint") == 0) {
- char buffer[64];
- size_t icy_metaint;
-
- if ((size_t)(end - header) >= sizeof(buffer) ||
- icy_defined(&c->icy_metadata))
- return size;
-
- memcpy(buffer, value, end - value);
- buffer[end - value] = 0;
-
- icy_metaint = g_ascii_strtoull(buffer, NULL, 10);
- g_debug("icy-metaint=%zu", icy_metaint);
-
- if (icy_metaint > 0) {
- icy_start(&c->icy_metadata, icy_metaint);
-
- /* a stream with icy-metadata is not
- seekable */
- c->base.seekable = false;
- }
- }
-
- return size;
-}
-
-/** called by curl when new data is available */
-static size_t
-input_curl_writefunction(void *ptr, size_t size, size_t nmemb, void *stream)
-{
- struct input_curl *c = (struct input_curl *)stream;
- struct buffer *buffer;
-
- size *= nmemb;
- if (size == 0)
- return 0;
-
- g_mutex_lock(c->base.mutex);
-
-#if LIBCURL_VERSION_NUM >= 0x071200
- if (curl_total_buffer_size(c) + size >= CURL_MAX_BUFFERED) {
- c->paused = true;
- g_mutex_unlock(c->base.mutex);
- return CURL_WRITEFUNC_PAUSE;
- }
-#endif
-
- buffer = g_malloc(sizeof(*buffer) - sizeof(buffer->data) + size);
- buffer->size = size;
- buffer->consumed = 0;
- memcpy(buffer->data, ptr, size);
-
- g_queue_push_tail(c->buffers, buffer);
- c->base.ready = true;
-
- g_cond_broadcast(c->base.cond);
- g_mutex_unlock(c->base.mutex);
-
- return size;
-}
-
-static bool
-input_curl_easy_init(struct input_curl *c, GError **error_r)
-{
- CURLcode code;
-
- c->easy = curl_easy_init();
- if (c->easy == NULL) {
- g_set_error(error_r, curl_quark(), 0,
- "curl_easy_init() failed");
- return false;
- }
-
- curl_easy_setopt(c->easy, CURLOPT_USERAGENT,
- "Music Player Daemon " VERSION);
- curl_easy_setopt(c->easy, CURLOPT_HEADERFUNCTION,
- input_curl_headerfunction);
- curl_easy_setopt(c->easy, CURLOPT_WRITEHEADER, c);
- curl_easy_setopt(c->easy, CURLOPT_WRITEFUNCTION,
- input_curl_writefunction);
- curl_easy_setopt(c->easy, CURLOPT_WRITEDATA, c);
- curl_easy_setopt(c->easy, CURLOPT_HTTP200ALIASES, http_200_aliases);
- curl_easy_setopt(c->easy, CURLOPT_FOLLOWLOCATION, 1);
- curl_easy_setopt(c->easy, CURLOPT_NETRC, 1);
- curl_easy_setopt(c->easy, CURLOPT_MAXREDIRS, 5);
- curl_easy_setopt(c->easy, CURLOPT_FAILONERROR, true);
- curl_easy_setopt(c->easy, CURLOPT_ERRORBUFFER, c->error);
- curl_easy_setopt(c->easy, CURLOPT_NOPROGRESS, 1l);
- curl_easy_setopt(c->easy, CURLOPT_NOSIGNAL, 1l);
- curl_easy_setopt(c->easy, CURLOPT_CONNECTTIMEOUT, 10l);
-
- if (proxy != NULL)
- curl_easy_setopt(c->easy, CURLOPT_PROXY, proxy);
-
- if (proxy_port > 0)
- curl_easy_setopt(c->easy, CURLOPT_PROXYPORT, (long)proxy_port);
-
- if (proxy_user != NULL && proxy_password != NULL) {
- char *proxy_auth_str =
- g_strconcat(proxy_user, ":", proxy_password, NULL);
- curl_easy_setopt(c->easy, CURLOPT_PROXYUSERPWD, proxy_auth_str);
- g_free(proxy_auth_str);
- }
-
- code = curl_easy_setopt(c->easy, CURLOPT_URL, c->url);
- if (code != CURLE_OK) {
- g_set_error(error_r, curl_quark(), code,
- "curl_easy_setopt() failed: %s",
- curl_easy_strerror(code));
- return false;
- }
-
- c->request_headers = NULL;
- c->request_headers = curl_slist_append(c->request_headers,
- "Icy-Metadata: 1");
- curl_easy_setopt(c->easy, CURLOPT_HTTPHEADER, c->request_headers);
-
- return true;
-}
-
-static bool
-input_curl_seek(struct input_stream *is, goffset offset, int whence,
- GError **error_r)
-{
- struct input_curl *c = (struct input_curl *)is;
- bool ret;
-
- assert(is->ready);
-
- if (whence == SEEK_SET && offset == is->offset)
- /* no-op */
- return true;
-
- if (!is->seekable)
- return false;
-
- /* calculate the absolute offset */
-
- switch (whence) {
- case SEEK_SET:
- break;
-
- case SEEK_CUR:
- offset += is->offset;
- break;
-
- case SEEK_END:
- if (is->size < 0)
- /* stream size is not known */
- return false;
-
- offset += is->size;
- break;
-
- default:
- return false;
- }
-
- if (offset < 0)
- return false;
-
- /* check if we can fast-forward the buffer */
-
- while (offset > is->offset && !g_queue_is_empty(c->buffers)) {
- struct buffer *buffer;
- size_t length;
-
- buffer = (struct buffer *)g_queue_pop_head(c->buffers);
-
- length = buffer->size - buffer->consumed;
- if (offset - is->offset < (goffset)length)
- length = offset - is->offset;
-
- buffer = consume_buffer(buffer, length);
- if (buffer != NULL)
- g_queue_push_head(c->buffers, buffer);
-
- is->offset += length;
- }
-
- if (offset == is->offset)
- return true;
-
- /* close the old connection and open a new one */
-
- g_mutex_unlock(c->base.mutex);
-
- input_curl_easy_free_indirect(c);
- input_curl_flush_buffers(c);
-
- is->offset = offset;
- if (is->offset == is->size) {
- /* seek to EOF: simulate empty result; avoid
- triggering a "416 Requested Range Not Satisfiable"
- response */
- return true;
- }
-
- ret = input_curl_easy_init(c, error_r);
- if (!ret)
- return false;
-
- /* send the "Range" header */
-
- if (is->offset > 0) {
- c->range = g_strdup_printf("%lld-", (long long)is->offset);
- curl_easy_setopt(c->easy, CURLOPT_RANGE, c->range);
- }
-
- c->base.ready = false;
-
- if (!input_curl_easy_add_indirect(c, error_r))
- return false;
-
- g_mutex_lock(c->base.mutex);
-
- while (!c->base.ready)
- g_cond_wait(c->base.cond, c->base.mutex);
-
- if (c->postponed_error != NULL) {
- g_propagate_error(error_r, c->postponed_error);
- c->postponed_error = NULL;
- return false;
- }
-
- return true;
-}
-
-static struct input_stream *
-input_curl_open(const char *url, GMutex *mutex, GCond *cond,
- GError **error_r)
-{
- assert(mutex != NULL);
- assert(cond != NULL);
-
- struct input_curl *c;
-
- if (strncmp(url, "http://", 7) != 0)
- return NULL;
-
- c = g_new0(struct input_curl, 1);
- input_stream_init(&c->base, &input_plugin_curl, url,
- mutex, cond);
-
- c->url = g_strdup(url);
- c->buffers = g_queue_new();
-
- icy_clear(&c->icy_metadata);
- c->tag = NULL;
-
- c->postponed_error = NULL;
-
-#if LIBCURL_VERSION_NUM >= 0x071200
- c->paused = false;
-#endif
-
- if (!input_curl_easy_init(c, error_r)) {
- input_curl_free(c);
- return NULL;
- }
-
- if (!input_curl_easy_add_indirect(c, error_r)) {
- input_curl_free(c);
- return NULL;
- }
-
- return &c->base;
-}
-
-const struct input_plugin input_plugin_curl = {
- .name = "curl",
- .init = input_curl_init,
- .finish = input_curl_finish,
-
- .open = input_curl_open,
- .close = input_curl_close,
- .check = input_curl_check,
- .tag = input_curl_tag,
- .available = input_curl_available,
- .read = input_curl_read,
- .eof = input_curl_eof,
- .seek = input_curl_seek,
-};
diff --git a/src/input/curl_input_plugin.h b/src/input/curl_input_plugin.h
deleted file mode 100644
index c6e71bf40..000000000
--- a/src/input/curl_input_plugin.h
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * 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_INPUT_CURL_H
-#define MPD_INPUT_CURL_H
-
-struct input_stream;
-
-extern const struct input_plugin input_plugin_curl;
-
-#endif
diff --git a/src/input/despotify_input_plugin.c b/src/input/despotify_input_plugin.c
deleted file mode 100644
index 200a0afd6..000000000
--- a/src/input/despotify_input_plugin.c
+++ /dev/null
@@ -1,230 +0,0 @@
-/*
- * Copyright (C) 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.
- */
-
-#include "config.h"
-#include "input/despotify_input_plugin.h"
-#include "input_internal.h"
-#include "input_plugin.h"
-#include "tag.h"
-#include "despotify_utils.h"
-
-#include <glib.h>
-
-#include <unistd.h>
-#include <string.h>
-#include <errno.h>
-#include <despotify.h>
-
-#include <stdio.h>
-
-struct input_despotify {
- struct input_stream base;
-
- struct despotify_session *session;
- struct ds_track *track;
- struct tag *tag;
- struct ds_pcm_data pcm;
- size_t len_available;
- bool eof;
-};
-
-
-static void
-refill_buffer(struct input_despotify *ctx)
-{
- /* Wait until there is data */
- while (1) {
- int rc = despotify_get_pcm(ctx->session, &ctx->pcm);
-
- if (rc == 0 && ctx->pcm.len) {
- ctx->len_available = ctx->pcm.len;
- break;
- }
- if (ctx->eof == true)
- break;
-
- if (rc < 0) {
- g_debug("despotify_get_pcm error\n");
- ctx->eof = true;
- break;
- }
-
- /* Wait a while until next iteration */
- usleep(50 * 1000);
- }
-}
-
-static void callback(G_GNUC_UNUSED struct despotify_session* ds,
- int sig, G_GNUC_UNUSED void* data, void* callback_data)
-{
- struct input_despotify *ctx = (struct input_despotify *)callback_data;
-
- switch (sig) {
- case DESPOTIFY_NEW_TRACK:
- break;
-
- case DESPOTIFY_TIME_TELL:
- break;
-
- case DESPOTIFY_TRACK_PLAY_ERROR:
- g_debug("Track play error\n");
- ctx->eof = true;
- ctx->len_available = 0;
- break;
-
- case DESPOTIFY_END_OF_PLAYLIST:
- ctx->eof = true;
- g_debug("End of playlist: %d\n", ctx->eof);
- break;
- }
-}
-
-
-static struct input_stream *
-input_despotify_open(const char *url,
- GMutex *mutex, GCond *cond,
- G_GNUC_UNUSED GError **error_r)
-{
- struct input_despotify *ctx;
- struct despotify_session *session;
- struct ds_link *ds_link;
- struct ds_track *track;
-
- if (!g_str_has_prefix(url, "spt://"))
- return NULL;
-
- session = mpd_despotify_get_session();
- if (!session)
- return NULL;
-
- ds_link = despotify_link_from_uri(url + 6);
- if (!ds_link) {
- g_debug("Can't find %s\n", url);
- return NULL;
- }
- if (ds_link->type != LINK_TYPE_TRACK) {
- despotify_free_link(ds_link);
- return NULL;
- }
-
- ctx = g_new(struct input_despotify, 1);
- memset(ctx, 0, sizeof(*ctx));
-
- track = despotify_link_get_track(session, ds_link);
- despotify_free_link(ds_link);
- if (!track) {
- g_free(ctx);
- return NULL;
- }
-
- input_stream_init(&ctx->base, &input_plugin_despotify, url,
- mutex, cond);
- ctx->session = session;
- ctx->track = track;
- ctx->tag = mpd_despotify_tag_from_track(track);
- ctx->eof = false;
- /* Despotify outputs pcm data */
- ctx->base.mime = g_strdup("audio/x-mpd-cdda-pcm");
- ctx->base.ready = true;
-
- if (!mpd_despotify_register_callback(callback, ctx)) {
- despotify_free_link(ds_link);
-
- return NULL;
- }
-
- if (despotify_play(ctx->session, ctx->track, false) == false) {
- despotify_free_track(ctx->track);
- g_free(ctx);
- return NULL;
- }
-
- return &ctx->base;
-}
-
-static size_t
-input_despotify_read(struct input_stream *is, void *ptr, size_t size,
- G_GNUC_UNUSED GError **error_r)
-{
- struct input_despotify *ctx = (struct input_despotify *)is;
- size_t to_cpy = size;
-
- if (ctx->len_available == 0)
- refill_buffer(ctx);
-
- if (ctx->len_available < size)
- to_cpy = ctx->len_available;
- memcpy(ptr, ctx->pcm.buf, to_cpy);
- ctx->len_available -= to_cpy;
-
- is->offset += to_cpy;
-
- return to_cpy;
-}
-
-static void
-input_despotify_close(struct input_stream *is)
-{
- struct input_despotify *ctx = (struct input_despotify *)is;
-
- if (ctx->tag != NULL)
- tag_free(ctx->tag);
-
- mpd_despotify_unregister_callback(callback);
- despotify_free_track(ctx->track);
- input_stream_deinit(&ctx->base);
- g_free(ctx);
-}
-
-static bool
-input_despotify_eof(struct input_stream *is)
-{
- struct input_despotify *ctx = (struct input_despotify *)is;
-
- return ctx->eof;
-}
-
-static bool
-input_despotify_seek(G_GNUC_UNUSED struct input_stream *is,
- G_GNUC_UNUSED goffset offset, G_GNUC_UNUSED int whence,
- G_GNUC_UNUSED GError **error_r)
-{
- return false;
-}
-
-static struct tag *
-input_despotify_tag(struct input_stream *is)
-{
- struct input_despotify *ctx = (struct input_despotify *)is;
- struct tag *tag = ctx->tag;
-
- ctx->tag = NULL;
-
- return tag;
-}
-
-const struct input_plugin input_plugin_despotify = {
- .name = "spt",
- .open = input_despotify_open,
- .close = input_despotify_close,
- .read = input_despotify_read,
- .eof = input_despotify_eof,
- .seek = input_despotify_seek,
- .tag = input_despotify_tag,
-};
diff --git a/src/input/despotify_input_plugin.h b/src/input/despotify_input_plugin.h
deleted file mode 100644
index 4c070d882..000000000
--- a/src/input/despotify_input_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 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 INPUT_DESPOTIFY_H
-#define INPUT_DESPOTIFY_H
-
-extern const struct input_plugin input_plugin_despotify;
-
-#endif
diff --git a/src/input/ffmpeg_input_plugin.c b/src/input/ffmpeg_input_plugin.c
deleted file mode 100644
index 6d339a067..000000000
--- a/src/input/ffmpeg_input_plugin.c
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "input/ffmpeg_input_plugin.h"
-#include "input_internal.h"
-#include "input_plugin.h"
-
-#include <libavutil/avutil.h>
-#include <libavformat/avio.h>
-#include <libavformat/avformat.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "input_ffmpeg"
-
-struct input_ffmpeg {
- struct input_stream base;
-
-#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0)
- AVIOContext *h;
-#else
- URLContext *h;
-#endif
-
- bool eof;
-};
-
-static inline GQuark
-ffmpeg_quark(void)
-{
- return g_quark_from_static_string("ffmpeg");
-}
-
-static inline bool
-input_ffmpeg_supported(void)
-{
-#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0)
- void *opaque = NULL;
- return avio_enum_protocols(&opaque, 0) != NULL;
-#else
- return av_protocol_next(NULL) != NULL;
-#endif
-}
-
-static bool
-input_ffmpeg_init(G_GNUC_UNUSED const struct config_param *param,
- G_GNUC_UNUSED GError **error_r)
-{
- av_register_all();
-
- /* disable this plugin if there's no registered protocol */
- if (!input_ffmpeg_supported()) {
- g_set_error(error_r, ffmpeg_quark(), 0,
- "No protocol");
- return false;
- }
-
- return true;
-}
-
-static struct input_stream *
-input_ffmpeg_open(const char *uri,
- GMutex *mutex, GCond *cond,
- GError **error_r)
-{
- struct input_ffmpeg *i;
-
- 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://"))
- return NULL;
-
- i = g_new(struct input_ffmpeg, 1);
- input_stream_init(&i->base, &input_plugin_ffmpeg, uri,
- mutex, cond);
-
-#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,1,0)
- int ret = avio_open(&i->h, uri, AVIO_FLAG_READ);
-#elif LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0)
- int ret = avio_open(&i->h, uri, AVIO_RDONLY);
-#else
- int ret = url_open(&i->h, uri, URL_RDONLY);
-#endif
- if (ret != 0) {
- g_free(i);
- g_set_error(error_r, ffmpeg_quark(), ret,
- "libavformat failed to open the URI");
- return NULL;
- }
-
- i->eof = false;
-
- i->base.ready = true;
-#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0)
- i->base.seekable = (i->h->seekable & AVIO_SEEKABLE_NORMAL) != 0;
- i->base.size = avio_size(i->h);
-#else
- i->base.seekable = !i->h->is_streamed;
- i->base.size = url_filesize(i->h);
-#endif
-
- /* hack to make MPD select the "ffmpeg" decoder plugin - since
- avio.h doesn't tell us the MIME type of the resource, we
- can't select a decoder plugin, but the "ffmpeg" plugin is
- quite good at auto-detection */
- i->base.mime = g_strdup("audio/x-mpd-ffmpeg");
-
- return &i->base;
-}
-
-static size_t
-input_ffmpeg_read(struct input_stream *is, void *ptr, size_t size,
- GError **error_r)
-{
- struct input_ffmpeg *i = (struct input_ffmpeg *)is;
-
-#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0)
- int ret = avio_read(i->h, ptr, size);
-#else
- int ret = url_read(i->h, ptr, size);
-#endif
- if (ret <= 0) {
- if (ret < 0)
- g_set_error(error_r, ffmpeg_quark(), 0,
- "url_read() failed");
-
- i->eof = true;
- return false;
- }
-
- is->offset += ret;
- return (size_t)ret;
-}
-
-static void
-input_ffmpeg_close(struct input_stream *is)
-{
- struct input_ffmpeg *i = (struct input_ffmpeg *)is;
-
-#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0)
- avio_close(i->h);
-#else
- url_close(i->h);
-#endif
- input_stream_deinit(&i->base);
- g_free(i);
-}
-
-static bool
-input_ffmpeg_eof(struct input_stream *is)
-{
- struct input_ffmpeg *i = (struct input_ffmpeg *)is;
-
- return i->eof;
-}
-
-static bool
-input_ffmpeg_seek(struct input_stream *is, goffset offset, int whence,
- G_GNUC_UNUSED GError **error_r)
-{
- struct input_ffmpeg *i = (struct input_ffmpeg *)is;
-#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0)
- int64_t ret = avio_seek(i->h, offset, whence);
-#else
- int64_t ret = url_seek(i->h, offset, whence);
-#endif
-
- if (ret >= 0) {
- i->eof = false;
- return true;
- } else {
- g_set_error(error_r, ffmpeg_quark(), 0, "url_seek() failed");
- return false;
- }
-}
-
-const struct input_plugin input_plugin_ffmpeg = {
- .name = "ffmpeg",
- .init = input_ffmpeg_init,
- .open = input_ffmpeg_open,
- .close = input_ffmpeg_close,
- .read = input_ffmpeg_read,
- .eof = input_ffmpeg_eof,
- .seek = input_ffmpeg_seek,
-};
diff --git a/src/input/ffmpeg_input_plugin.h b/src/input/ffmpeg_input_plugin.h
deleted file mode 100644
index 393836ca5..000000000
--- a/src/input/ffmpeg_input_plugin.h
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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_FFMPEG_INPUT_PLUGIN_H
-#define MPD_FFMPEG_INPUT_PLUGIN_H
-
-/**
- * An input plugin based on libavformat's "avio" library.
- */
-extern const struct input_plugin input_plugin_ffmpeg;
-
-#endif
diff --git a/src/input/file_input_plugin.c b/src/input/file_input_plugin.c
deleted file mode 100644
index 5ee3f200b..000000000
--- a/src/input/file_input_plugin.c
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h" /* must be first for large file support */
-#include "input/file_input_plugin.h"
-#include "input_internal.h"
-#include "input_plugin.h"
-#include "fd_util.h"
-#include "open.h"
-
-#include <sys/stat.h>
-#include <unistd.h>
-#include <errno.h>
-#include <string.h>
-#include <glib.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "input_file"
-
-struct file_input_stream {
- struct input_stream base;
-
- int fd;
-};
-
-static inline GQuark
-file_quark(void)
-{
- return g_quark_from_static_string("file");
-}
-
-static struct input_stream *
-input_file_open(const char *filename,
- GMutex *mutex, GCond *cond,
- GError **error_r)
-{
- int fd, ret;
- struct stat st;
- struct file_input_stream *fis;
-
- if (!g_path_is_absolute(filename))
- return NULL;
-
- fd = open_cloexec(filename, O_RDONLY|O_BINARY, 0);
- if (fd < 0) {
- if (errno != ENOENT && errno != ENOTDIR)
- g_set_error(error_r, file_quark(), errno,
- "Failed to open \"%s\": %s",
- filename, g_strerror(errno));
- return NULL;
- }
-
- ret = fstat(fd, &st);
- if (ret < 0) {
- g_set_error(error_r, file_quark(), errno,
- "Failed to stat \"%s\": %s",
- filename, g_strerror(errno));
- close(fd);
- return NULL;
- }
-
- if (!S_ISREG(st.st_mode)) {
- g_set_error(error_r, file_quark(), 0,
- "Not a regular file: %s", filename);
- close(fd);
- return NULL;
- }
-
-#ifdef POSIX_FADV_SEQUENTIAL
- posix_fadvise(fd, (off_t)0, st.st_size, POSIX_FADV_SEQUENTIAL);
-#endif
-
- fis = g_new(struct file_input_stream, 1);
- input_stream_init(&fis->base, &input_plugin_file, filename,
- mutex, cond);
-
- fis->base.size = st.st_size;
- fis->base.seekable = true;
- fis->base.ready = true;
-
- fis->fd = fd;
-
- return &fis->base;
-}
-
-static bool
-input_file_seek(struct input_stream *is, goffset offset, int whence,
- GError **error_r)
-{
- struct file_input_stream *fis = (struct file_input_stream *)is;
-
- offset = (goffset)lseek(fis->fd, (off_t)offset, whence);
- if (offset < 0) {
- g_set_error(error_r, file_quark(), errno,
- "Failed to seek: %s", g_strerror(errno));
- return false;
- }
-
- is->offset = offset;
- return true;
-}
-
-static size_t
-input_file_read(struct input_stream *is, void *ptr, size_t size,
- GError **error_r)
-{
- struct file_input_stream *fis = (struct file_input_stream *)is;
- ssize_t nbytes;
-
- nbytes = read(fis->fd, ptr, size);
- if (nbytes < 0) {
- g_set_error(error_r, file_quark(), errno,
- "Failed to read: %s", g_strerror(errno));
- return 0;
- }
-
- is->offset += nbytes;
- return (size_t)nbytes;
-}
-
-static void
-input_file_close(struct input_stream *is)
-{
- struct file_input_stream *fis = (struct file_input_stream *)is;
-
- close(fis->fd);
- input_stream_deinit(&fis->base);
- g_free(fis);
-}
-
-static bool
-input_file_eof(struct input_stream *is)
-{
- return is->offset >= is->size;
-}
-
-const struct input_plugin input_plugin_file = {
- .name = "file",
- .open = input_file_open,
- .close = input_file_close,
- .read = input_file_read,
- .eof = input_file_eof,
- .seek = input_file_seek,
-};
diff --git a/src/input/file_input_plugin.h b/src/input/file_input_plugin.h
deleted file mode 100644
index f24769d57..000000000
--- a/src/input/file_input_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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_INPUT_FILE_H
-#define MPD_INPUT_FILE_H
-
-extern const struct input_plugin input_plugin_file;
-
-#endif
diff --git a/src/input/mms_input_plugin.c b/src/input/mms_input_plugin.c
deleted file mode 100644
index cff15125b..000000000
--- a/src/input/mms_input_plugin.c
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "input/mms_input_plugin.h"
-#include "input_internal.h"
-#include "input_plugin.h"
-
-#include <glib.h>
-#include <libmms/mmsx.h>
-
-#include <string.h>
-#include <errno.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "input_mms"
-
-struct input_mms {
- struct input_stream base;
-
- mmsx_t *mms;
-
- bool eof;
-};
-
-static inline GQuark
-mms_quark(void)
-{
- return g_quark_from_static_string("mms");
-}
-
-static struct input_stream *
-input_mms_open(const char *url,
- GMutex *mutex, GCond *cond,
- GError **error_r)
-{
- struct input_mms *m;
-
- 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://"))
- return NULL;
-
- m = g_new(struct input_mms, 1);
- input_stream_init(&m->base, &input_plugin_mms, url,
- mutex, cond);
-
- m->mms = mmsx_connect(NULL, NULL, url, 128 * 1024);
- if (m->mms == NULL) {
- g_free(m);
- g_set_error(error_r, mms_quark(), 0, "mmsx_connect() failed");
- return NULL;
- }
-
- m->eof = false;
-
- /* XX is this correct? at least this selects the ffmpeg
- decoder, which seems to work fine*/
- m->base.mime = g_strdup("audio/x-ms-wma");
-
- m->base.ready = true;
-
- return &m->base;
-}
-
-static size_t
-input_mms_read(struct input_stream *is, void *ptr, size_t size,
- GError **error_r)
-{
- struct input_mms *m = (struct input_mms *)is;
- int ret;
-
- ret = mmsx_read(NULL, m->mms, ptr, size);
- if (ret <= 0) {
- if (ret < 0) {
- g_set_error(error_r, mms_quark(), errno,
- "mmsx_read() failed: %s",
- g_strerror(errno));
- }
-
- m->eof = true;
- return false;
- }
-
- is->offset += ret;
-
- return (size_t)ret;
-}
-
-static void
-input_mms_close(struct input_stream *is)
-{
- struct input_mms *m = (struct input_mms *)is;
-
- mmsx_close(m->mms);
- input_stream_deinit(&m->base);
- g_free(m);
-}
-
-static bool
-input_mms_eof(struct input_stream *is)
-{
- struct input_mms *m = (struct input_mms *)is;
-
- return m->eof;
-}
-
-static bool
-input_mms_seek(G_GNUC_UNUSED struct input_stream *is,
- G_GNUC_UNUSED goffset offset, G_GNUC_UNUSED int whence,
- G_GNUC_UNUSED GError **error_r)
-{
- return false;
-}
-
-const struct input_plugin input_plugin_mms = {
- .name = "mms",
- .open = input_mms_open,
- .close = input_mms_close,
- .read = input_mms_read,
- .eof = input_mms_eof,
- .seek = input_mms_seek,
-};
diff --git a/src/input/rewind_input_plugin.c b/src/input/rewind_input_plugin.c
deleted file mode 100644
index cf06fc57b..000000000
--- a/src/input/rewind_input_plugin.c
+++ /dev/null
@@ -1,256 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "input/rewind_input_plugin.h"
-#include "input_internal.h"
-#include "input_plugin.h"
-#include "tag.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <stdio.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "input_rewind"
-
-struct input_rewind {
- struct input_stream base;
-
- struct input_stream *input;
-
- /**
- * The read position within the buffer. Undefined as long as
- * reading_from_buffer() returns false.
- */
- size_t head;
-
- /**
- * The write/append position within the buffer.
- */
- size_t tail;
-
- /**
- * The size of this buffer is the maximum number of bytes
- * which can be rewinded cheaply without passing the "seek"
- * call to CURL.
- *
- * The origin of this buffer is always the beginning of the
- * stream (offset 0).
- */
- char buffer[64 * 1024];
-};
-
-/**
- * Are we currently reading from the buffer, and does the buffer
- * contain more data for the next read operation?
- */
-static bool
-reading_from_buffer(const struct input_rewind *r)
-{
- return r->tail > 0 && r->base.offset < r->input->offset;
-}
-
-/**
- * Copy public attributes from the underlying input stream to the
- * "rewind" input stream. This function is called when a method of
- * the underlying stream has returned, which may have modified these
- * attributes.
- */
-static void
-copy_attributes(struct input_rewind *r)
-{
- struct input_stream *dest = &r->base;
- const struct input_stream *src = r->input;
-
- assert(dest != src);
- assert(src->mime == NULL || dest->mime != src->mime);
-
- bool dest_ready = dest->ready;
-
- dest->ready = src->ready;
- dest->seekable = src->seekable;
- dest->size = src->size;
- dest->offset = src->offset;
-
- if (!dest_ready && src->ready) {
- g_free(dest->mime);
- dest->mime = g_strdup(src->mime);
- }
-}
-
-static void
-input_rewind_close(struct input_stream *is)
-{
- struct input_rewind *r = (struct input_rewind *)is;
-
- input_stream_close(r->input);
-
- input_stream_deinit(&r->base);
- g_free(r);
-}
-
-static bool
-input_rewind_check(struct input_stream *is, GError **error_r)
-{
- struct input_rewind *r = (struct input_rewind *)is;
-
- return input_stream_check(r->input, error_r);
-}
-
-static void
-input_rewind_update(struct input_stream *is)
-{
- struct input_rewind *r = (struct input_rewind *)is;
-
- if (!reading_from_buffer(r))
- copy_attributes(r);
-}
-
-static struct tag *
-input_rewind_tag(struct input_stream *is)
-{
- struct input_rewind *r = (struct input_rewind *)is;
-
- return input_stream_tag(r->input);
-}
-
-static bool
-input_rewind_available(struct input_stream *is)
-{
- struct input_rewind *r = (struct input_rewind *)is;
-
- return input_stream_available(r->input);
-}
-
-static size_t
-input_rewind_read(struct input_stream *is, void *ptr, size_t size,
- GError **error_r)
-{
- struct input_rewind *r = (struct input_rewind *)is;
-
- if (reading_from_buffer(r)) {
- /* buffered read */
-
- assert(r->head == (size_t)is->offset);
- assert(r->tail == (size_t)r->input->offset);
-
- if (size > r->tail - r->head)
- size = r->tail - r->head;
-
- memcpy(ptr, r->buffer + r->head, size);
- r->head += size;
- is->offset += size;
-
- return size;
- } else {
- /* pass method call to underlying stream */
-
- size_t nbytes = input_stream_read(r->input, ptr, size, error_r);
-
- if (r->input->offset > (goffset)sizeof(r->buffer))
- /* disable buffering */
- r->tail = 0;
- else if (r->tail == (size_t)is->offset) {
- /* append to buffer */
-
- memcpy(r->buffer + r->tail, ptr, nbytes);
- r->tail += nbytes;
-
- assert(r->tail == (size_t)r->input->offset);
- }
-
- copy_attributes(r);
-
- return nbytes;
- }
-}
-
-static bool
-input_rewind_eof(struct input_stream *is)
-{
- struct input_rewind *r = (struct input_rewind *)is;
-
- return !reading_from_buffer(r) && input_stream_eof(r->input);
-}
-
-static bool
-input_rewind_seek(struct input_stream *is, goffset offset, int whence,
- GError **error_r)
-{
- struct input_rewind *r = (struct input_rewind *)is;
-
- assert(is->ready);
-
- if (whence == SEEK_SET && r->tail > 0 && offset <= (goffset)r->tail) {
- /* buffered seek */
-
- assert(!reading_from_buffer(r) ||
- r->head == (size_t)is->offset);
- assert(r->tail == (size_t)r->input->offset);
-
- r->head = (size_t)offset;
- is->offset = offset;
-
- return true;
- } else {
- bool success = input_stream_seek(r->input, offset, whence,
- error_r);
- copy_attributes(r);
-
- /* disable the buffer, because r->input has left the
- buffered range now */
- r->tail = 0;
-
- return success;
- }
-}
-
-static const struct input_plugin rewind_input_plugin = {
- .close = input_rewind_close,
- .check = input_rewind_check,
- .update = input_rewind_update,
- .tag = input_rewind_tag,
- .available = input_rewind_available,
- .read = input_rewind_read,
- .eof = input_rewind_eof,
- .seek = input_rewind_seek,
-};
-
-struct input_stream *
-input_rewind_open(struct input_stream *is)
-{
- struct input_rewind *c;
-
- assert(is != NULL);
- assert(is->offset == 0);
-
- if (is->seekable)
- /* seekable resources don't need this plugin */
- return is;
-
- c = g_new(struct input_rewind, 1);
- input_stream_init(&c->base, &rewind_input_plugin, is->uri,
- is->mutex, is->cond);
- c->tail = 0;
- c->input = is;
-
- return &c->base;
-}
diff --git a/src/input/rewind_input_plugin.h b/src/input/rewind_input_plugin.h
deleted file mode 100644
index 83abe257a..000000000
--- a/src/input/rewind_input_plugin.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * 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.
- */
-
-/** \file
- *
- * A wrapper for an input_stream object which allows cheap buffered
- * rewinding. This is useful while detecting the stream codec (let
- * each decoder plugin peek a portion from the stream).
- */
-
-#ifndef MPD_INPUT_REWIND_H
-#define MPD_INPUT_REWIND_H
-
-#include "check.h"
-
-struct input_stream;
-
-struct input_stream *
-input_rewind_open(struct input_stream *is);
-
-#endif
diff --git a/src/input/soup_input_plugin.c b/src/input/soup_input_plugin.c
deleted file mode 100644
index fc903b48c..000000000
--- a/src/input/soup_input_plugin.c
+++ /dev/null
@@ -1,473 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "input/soup_input_plugin.h"
-#include "input_internal.h"
-#include "input_plugin.h"
-#include "io_thread.h"
-#include "conf.h"
-
-#include <libsoup/soup-uri.h>
-#include <libsoup/soup-session-async.h>
-
-#include <assert.h>
-#include <string.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "input_soup"
-
-/**
- * Do not buffer more than this number of bytes. It should be a
- * reasonable limit that doesn't make low-end machines suffer too
- * much, but doesn't cause stuttering on high-latency lines.
- */
-static const size_t SOUP_MAX_BUFFERED = 512 * 1024;
-
-/**
- * Resume the stream at this number of bytes after it has been paused.
- */
-static const size_t SOUP_RESUME_AT = 384 * 1024;
-
-static SoupURI *soup_proxy;
-static SoupSession *soup_session;
-
-struct input_soup {
- struct input_stream base;
-
- SoupMessage *msg;
-
- GQueue *buffers;
-
- size_t current_consumed;
-
- size_t total_buffered;
-
- bool alive, pause, eof;
-
- /**
- * Set when the session callback has been invoked, when it is
- * safe to free this object.
- */
- bool completed;
-
- GError *postponed_error;
-};
-
-static inline GQuark
-soup_quark(void)
-{
- return g_quark_from_static_string("soup");
-}
-
-static bool
-input_soup_init(const struct config_param *param, GError **error_r)
-{
- assert(soup_proxy == NULL);
- assert(soup_session == NULL);
-
- g_type_init();
-
- const char *proxy = config_get_block_string(param, "proxy", NULL);
-
- if (proxy != NULL) {
- soup_proxy = soup_uri_new(proxy);
- if (soup_proxy == NULL) {
- g_set_error(error_r, soup_quark(), 0,
- "failed to parse proxy setting");
- return false;
- }
- }
-
- soup_session =
- soup_session_async_new_with_options(SOUP_SESSION_PROXY_URI,
- soup_proxy,
- SOUP_SESSION_ASYNC_CONTEXT,
- io_thread_context(),
- NULL);
-
- return true;
-}
-
-static void
-input_soup_finish(void)
-{
- assert(soup_session != NULL);
-
- soup_session_abort(soup_session);
- g_object_unref(G_OBJECT(soup_session));
-
- if (soup_proxy != NULL)
- soup_uri_free(soup_proxy);
-}
-
-/**
- * Copy the error from the SoupMessage object to
- * input_soup::postponed_error.
- *
- * @return true if there was no error
- */
-static bool
-input_soup_copy_error(struct input_soup *s, const SoupMessage *msg)
-{
- if (SOUP_STATUS_IS_SUCCESSFUL(msg->status_code))
- return true;
-
- if (msg->status_code == SOUP_STATUS_CANCELLED)
- /* failure, but don't generate a GError, because this
- status was caused by _close() */
- return false;
-
- if (s->postponed_error != NULL)
- /* there's already a GError, don't overwrite it */
- return false;
-
- if (SOUP_STATUS_IS_TRANSPORT_ERROR(msg->status_code))
- s->postponed_error =
- g_error_new(soup_quark(), msg->status_code,
- "HTTP client error: %s",
- msg->reason_phrase);
- else
- s->postponed_error =
- g_error_new(soup_quark(), msg->status_code,
- "got HTTP status: %d %s",
- msg->status_code, msg->reason_phrase);
-
- return false;
-}
-
-static void
-input_soup_session_callback(G_GNUC_UNUSED SoupSession *session,
- SoupMessage *msg, gpointer user_data)
-{
- struct input_soup *s = user_data;
-
- assert(msg == s->msg);
- assert(!s->completed);
-
- g_mutex_lock(s->base.mutex);
-
- if (!s->base.ready)
- input_soup_copy_error(s, msg);
-
- s->base.ready = true;
- s->alive = false;
- s->completed = true;
-
- g_cond_broadcast(s->base.cond);
- g_mutex_unlock(s->base.mutex);
-}
-
-static void
-input_soup_got_headers(SoupMessage *msg, gpointer user_data)
-{
- struct input_soup *s = user_data;
-
- g_mutex_lock(s->base.mutex);
-
- if (!input_soup_copy_error(s, msg)) {
- g_mutex_unlock(s->base.mutex);
-
- soup_session_cancel_message(soup_session, msg,
- SOUP_STATUS_CANCELLED);
- return;
- }
-
- s->base.ready = true;
- g_cond_broadcast(s->base.cond);
- g_mutex_unlock(s->base.mutex);
-
- soup_message_body_set_accumulate(msg->response_body, false);
-}
-
-static void
-input_soup_got_chunk(SoupMessage *msg, SoupBuffer *chunk, gpointer user_data)
-{
- struct input_soup *s = user_data;
-
- assert(msg == s->msg);
-
- g_mutex_lock(s->base.mutex);
-
- g_queue_push_tail(s->buffers, soup_buffer_copy(chunk));
- s->total_buffered += chunk->length;
-
- if (s->total_buffered >= SOUP_MAX_BUFFERED && !s->pause) {
- s->pause = true;
- soup_session_pause_message(soup_session, msg);
- }
-
- g_cond_broadcast(s->base.cond);
- g_mutex_unlock(s->base.mutex);
-}
-
-static void
-input_soup_got_body(G_GNUC_UNUSED SoupMessage *msg, gpointer user_data)
-{
- struct input_soup *s = user_data;
-
- assert(msg == s->msg);
-
- g_mutex_lock(s->base.mutex);
-
- s->base.ready = true;
- s->eof = true;
- s->alive = false;
-
- g_cond_broadcast(s->base.cond);
- g_mutex_unlock(s->base.mutex);
-}
-
-static bool
-input_soup_wait_data(struct input_soup *s)
-{
- while (true) {
- if (s->eof)
- return true;
-
- if (!s->alive)
- return false;
-
- if (!g_queue_is_empty(s->buffers))
- return true;
-
- assert(s->current_consumed == 0);
-
- g_cond_wait(s->base.cond, s->base.mutex);
- }
-}
-
-static gpointer
-input_soup_queue(gpointer data)
-{
- struct input_soup *s = data;
-
- soup_session_queue_message(soup_session, s->msg,
- input_soup_session_callback, s);
-
- return NULL;
-}
-
-static struct input_stream *
-input_soup_open(const char *uri,
- GMutex *mutex, GCond *cond,
- G_GNUC_UNUSED GError **error_r)
-{
- if (strncmp(uri, "http://", 7) != 0)
- return NULL;
-
- struct input_soup *s = g_new(struct input_soup, 1);
- input_stream_init(&s->base, &input_plugin_soup, uri,
- mutex, cond);
-
- s->buffers = g_queue_new();
- s->current_consumed = 0;
- s->total_buffered = 0;
-
-#if GCC_CHECK_VERSION(4,6)
-#pragma GCC diagnostic push
- /* the libsoup macro SOUP_METHOD_GET discards the "const"
- attribute of the g_intern_static_string() return value;
- don't make the gcc warning fatal: */
-#pragma GCC diagnostic ignored "-Wcast-qual"
-#endif
-
- s->msg = soup_message_new(SOUP_METHOD_GET, uri);
-
-#if GCC_CHECK_VERSION(4,6)
-#pragma GCC diagnostic pop
-#endif
-
- soup_message_set_flags(s->msg, SOUP_MESSAGE_NO_REDIRECT);
-
- soup_message_headers_append(s->msg->request_headers, "User-Agent",
- "Music Player Daemon " VERSION);
-
- g_signal_connect(s->msg, "got-headers",
- G_CALLBACK(input_soup_got_headers), s);
- g_signal_connect(s->msg, "got-chunk",
- G_CALLBACK(input_soup_got_chunk), s);
- g_signal_connect(s->msg, "got-body",
- G_CALLBACK(input_soup_got_body), s);
-
- s->alive = true;
- s->pause = false;
- s->eof = false;
- s->completed = false;
- s->postponed_error = NULL;
-
- io_thread_call(input_soup_queue, s);
-
- return &s->base;
-}
-
-static gpointer
-input_soup_cancel(gpointer data)
-{
- struct input_soup *s = data;
-
- if (!s->completed)
- soup_session_cancel_message(soup_session, s->msg,
- SOUP_STATUS_CANCELLED);
-
- return NULL;
-}
-
-static void
-input_soup_close(struct input_stream *is)
-{
- struct input_soup *s = (struct input_soup *)is;
-
- g_mutex_lock(s->base.mutex);
-
- if (!s->completed) {
- /* the messages's session callback hasn't been invoked
- yet; cancel it and wait for completion */
-
- g_mutex_unlock(s->base.mutex);
-
- io_thread_call(input_soup_cancel, s);
-
- g_mutex_lock(s->base.mutex);
- while (!s->completed)
- g_cond_wait(s->base.cond, s->base.mutex);
- }
-
- g_mutex_unlock(s->base.mutex);
-
- SoupBuffer *buffer;
- while ((buffer = g_queue_pop_head(s->buffers)) != NULL)
- soup_buffer_free(buffer);
- g_queue_free(s->buffers);
-
- if (s->postponed_error != NULL)
- g_error_free(s->postponed_error);
-
- input_stream_deinit(&s->base);
- g_free(s);
-}
-
-static bool
-input_soup_check(struct input_stream *is, GError **error_r)
-{
- struct input_soup *s = (struct input_soup *)is;
-
- bool success = s->postponed_error == NULL;
- if (!success) {
- g_propagate_error(error_r, s->postponed_error);
- s->postponed_error = NULL;
- }
-
- return success;
-}
-
-static bool
-input_soup_available(struct input_stream *is)
-{
- struct input_soup *s = (struct input_soup *)is;
-
- return s->eof || !s->alive || !g_queue_is_empty(s->buffers);
-}
-
-static size_t
-input_soup_read(struct input_stream *is, void *ptr, size_t size,
- G_GNUC_UNUSED GError **error_r)
-{
- struct input_soup *s = (struct input_soup *)is;
-
- if (!input_soup_wait_data(s)) {
- assert(!s->alive);
-
- if (s->postponed_error != NULL) {
- g_propagate_error(error_r, s->postponed_error);
- s->postponed_error = NULL;
- } else
- g_set_error_literal(error_r, soup_quark(), 0,
- "HTTP failure");
- return 0;
- }
-
- char *p0 = ptr, *p = p0, *p_end = p0 + size;
-
- while (p < p_end) {
- SoupBuffer *buffer = g_queue_pop_head(s->buffers);
- if (buffer == NULL) {
- assert(s->current_consumed == 0);
- break;
- }
-
- assert(s->current_consumed < buffer->length);
- assert(s->total_buffered >= buffer->length);
-
- const char *q = buffer->data;
- q += s->current_consumed;
-
- size_t remaining = buffer->length - s->current_consumed;
- size_t nbytes = p_end - p;
- if (nbytes > remaining)
- nbytes = remaining;
-
- memcpy(p, q, nbytes);
- p += nbytes;
-
- s->current_consumed += remaining;
- if (s->current_consumed >= buffer->length) {
- /* done with this buffer */
- s->total_buffered -= buffer->length;
- soup_buffer_free(buffer);
- s->current_consumed = 0;
- } else {
- /* partial read */
- assert(p == p_end);
-
- g_queue_push_head(s->buffers, buffer);
- }
- }
-
- if (s->pause && s->total_buffered < SOUP_RESUME_AT) {
- s->pause = false;
- soup_session_unpause_message(soup_session, s->msg);
- }
-
- size_t nbytes = p - p0;
- s->base.offset += nbytes;
-
- return nbytes;
-}
-
-static bool
-input_soup_eof(G_GNUC_UNUSED struct input_stream *is)
-{
- struct input_soup *s = (struct input_soup *)is;
-
- return !s->alive && g_queue_is_empty(s->buffers);
-}
-
-const struct input_plugin input_plugin_soup = {
- .name = "soup",
- .init = input_soup_init,
- .finish = input_soup_finish,
-
- .open = input_soup_open,
- .close = input_soup_close,
- .check = input_soup_check,
- .available = input_soup_available,
- .read = input_soup_read,
- .eof = input_soup_eof,
-};
diff --git a/src/input/soup_input_plugin.h b/src/input/soup_input_plugin.h
deleted file mode 100644
index 689b2d971..000000000
--- a/src/input/soup_input_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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_INPUT_SOUP_H
-#define MPD_INPUT_SOUP_H
-
-extern const struct input_plugin input_plugin_soup;
-
-#endif
diff --git a/src/input_init.c b/src/input_init.c
deleted file mode 100644
index 771d648d1..000000000
--- a/src/input_init.c
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "input_init.h"
-#include "input_plugin.h"
-#include "input_registry.h"
-#include "conf.h"
-
-#include <assert.h>
-#include <string.h>
-
-static inline GQuark
-input_quark(void)
-{
- return g_quark_from_static_string("input");
-}
-
-/**
- * Find the "input" configuration block for the specified plugin.
- *
- * @param plugin_name the name of the input plugin
- * @return the configuration block, or NULL if none was configured
- */
-static const struct config_param *
-input_plugin_config(const char *plugin_name, GError **error_r)
-{
- const struct config_param *param = NULL;
-
- while ((param = config_get_next_param(CONF_INPUT, param)) != NULL) {
- const char *name =
- config_get_block_string(param, "plugin", NULL);
- if (name == NULL) {
- g_set_error(error_r, input_quark(), 0,
- "input configuration without 'plugin' name in line %d",
- param->line);
- return NULL;
- }
-
- if (strcmp(name, plugin_name) == 0)
- return param;
- }
-
- return NULL;
-}
-
-bool
-input_stream_global_init(GError **error_r)
-{
- GError *error = NULL;
-
- for (unsigned i = 0; input_plugins[i] != NULL; ++i) {
- const struct input_plugin *plugin = input_plugins[i];
-
- assert(plugin->name != NULL);
- assert(*plugin->name != 0);
- assert(plugin->open != NULL);
-
- const struct config_param *param =
- input_plugin_config(plugin->name, &error);
- if (param == NULL && error != NULL) {
- g_propagate_error(error_r, error);
- return false;
- }
-
- if (!config_get_block_bool(param, "enabled", true))
- /* the plugin is disabled in mpd.conf */
- continue;
-
- if (plugin->init == NULL || plugin->init(param, &error))
- input_plugins_enabled[i] = true;
- else {
- g_propagate_prefixed_error(error_r, error,
- "Failed to initialize input plugin '%s': ",
- plugin->name);
- return false;
- }
- }
-
- return true;
-}
-
-void input_stream_global_finish(void)
-{
- input_plugins_for_each_enabled(plugin)
- if (plugin->finish != NULL)
- plugin->finish();
-}
diff --git a/src/input_init.h b/src/input_init.h
deleted file mode 100644
index ad92cda08..000000000
--- a/src/input_init.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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_INPUT_INIT_H
-#define MPD_INPUT_INIT_H
-
-#include "check.h"
-
-#include <glib.h>
-#include <stdbool.h>
-
-/**
- * Initializes this library and all input_stream implementations.
- *
- * @param error_r location to store the error occurring, or NULL to
- * ignore errors
- */
-bool
-input_stream_global_init(GError **error_r);
-
-/**
- * Deinitializes this library and all input_stream implementations.
- */
-void input_stream_global_finish(void);
-
-#endif
diff --git a/src/input_internal.c b/src/input_internal.c
deleted file mode 100644
index 92a71856e..000000000
--- a/src/input_internal.c
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "input_internal.h"
-#include "input_stream.h"
-
-#include <assert.h>
-
-void
-input_stream_init(struct input_stream *is, const struct input_plugin *plugin,
- const char *uri, GMutex *mutex, GCond *cond)
-{
- assert(is != NULL);
- assert(plugin != NULL);
- assert(uri != NULL);
-
- is->plugin = plugin;
- is->uri = g_strdup(uri);
- is->mutex = mutex;
- is->cond = cond;
- is->ready = false;
- is->seekable = false;
- is->size = -1;
- is->offset = 0;
- is->mime = NULL;
-}
-
-void
-input_stream_deinit(struct input_stream *is)
-{
- assert(is != NULL);
- assert(is->plugin != NULL);
-
- g_free(is->uri);
- g_free(is->mime);
-}
-
-void
-input_stream_signal_client(struct input_stream *is)
-{
- if (is->cond != NULL)
- g_cond_broadcast(is->cond);
-}
-
-void
-input_stream_set_ready(struct input_stream *is)
-{
- g_mutex_lock(is->mutex);
-
- if (!is->ready) {
- is->ready = true;
- input_stream_signal_client(is);
- }
-
- g_mutex_unlock(is->mutex);
-}
diff --git a/src/input_internal.h b/src/input_internal.h
deleted file mode 100644
index d95142e46..000000000
--- a/src/input_internal.h
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * 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_INPUT_INTERNAL_H
-#define MPD_INPUT_INTERNAL_H
-
-#include "check.h"
-
-#include <glib.h>
-
-struct input_stream;
-struct input_plugin;
-
-void
-input_stream_init(struct input_stream *is, const struct input_plugin *plugin,
- const char *uri, GMutex *mutex, GCond *cond);
-
-void
-input_stream_deinit(struct input_stream *is);
-
-void
-input_stream_signal_client(struct input_stream *is);
-
-void
-input_stream_set_ready(struct input_stream *is);
-
-#endif
diff --git a/src/input_plugin.h b/src/input_plugin.h
deleted file mode 100644
index 6b0c77c85..000000000
--- a/src/input_plugin.h
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * 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_INPUT_PLUGIN_H
-#define MPD_INPUT_PLUGIN_H
-
-#include "input_stream.h"
-
-#include <stddef.h>
-#include <stdbool.h>
-#include <sys/types.h>
-
-struct config_param;
-struct input_stream;
-
-struct input_plugin {
- const char *name;
-
- /**
- * Global initialization. This method is called when MPD starts.
- *
- * @param error_r location to store the error occurring, or
- * NULL to ignore errors
- * @return true on success, false if the plugin should be
- * disabled
- */
- bool (*init)(const struct config_param *param, GError **error_r);
-
- /**
- * Global deinitialization. Called once before MPD shuts
- * down (only if init() has returned true).
- */
- void (*finish)(void);
-
- struct input_stream *(*open)(const char *uri,
- GMutex *mutex, GCond *cond,
- GError **error_r);
- void (*close)(struct input_stream *is);
-
- /**
- * Check for errors that may have occurred in the I/O thread.
- * May be unimplemented for synchronous plugins.
- *
- * @return false on error
- */
- bool (*check)(struct input_stream *is, GError **error_r);
-
- /**
- * Update the public attributes. Call before access. Can be
- * NULL if the plugin always keeps its attributes up to date.
- */
- void (*update)(struct input_stream *is);
-
- struct tag *(*tag)(struct input_stream *is);
-
- /**
- * Returns true if the next read operation will not block:
- * either data is available, or end-of-stream has been
- * reached, or an error has occurred.
- *
- * If this method is unimplemented, then it is assumed that
- * reading will never block.
- */
- bool (*available)(struct input_stream *is);
-
- size_t (*read)(struct input_stream *is, void *ptr, size_t size,
- GError **error_r);
- bool (*eof)(struct input_stream *is);
- bool (*seek)(struct input_stream *is, goffset offset, int whence,
- GError **error_r);
-};
-
-#endif
diff --git a/src/input_registry.c b/src/input_registry.c
deleted file mode 100644
index 5987d5da2..000000000
--- a/src/input_registry.c
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "input_registry.h"
-#include "input/file_input_plugin.h"
-
-#ifdef ENABLE_ARCHIVE
-#include "input/archive_input_plugin.h"
-#endif
-
-#ifdef ENABLE_CURL
-#include "input/curl_input_plugin.h"
-#endif
-
-#ifdef ENABLE_SOUP
-#include "input/soup_input_plugin.h"
-#endif
-
-#ifdef HAVE_FFMPEG
-#include "input/ffmpeg_input_plugin.h"
-#endif
-
-#ifdef ENABLE_MMS
-#include "input/mms_input_plugin.h"
-#endif
-
-#ifdef ENABLE_CDIO_PARANOIA
-#include "input/cdio_paranoia_input_plugin.h"
-#endif
-
-#ifdef ENABLE_DESPOTIFY
-#include "input/despotify_input_plugin.h"
-#endif
-
-#include <glib.h>
-
-const struct input_plugin *const input_plugins[] = {
- &input_plugin_file,
-#ifdef ENABLE_ARCHIVE
- &input_plugin_archive,
-#endif
-#ifdef ENABLE_CURL
- &input_plugin_curl,
-#endif
-#ifdef ENABLE_SOUP
- &input_plugin_soup,
-#endif
-#ifdef HAVE_FFMPEG
- &input_plugin_ffmpeg,
-#endif
-#ifdef ENABLE_MMS
- &input_plugin_mms,
-#endif
-#ifdef ENABLE_CDIO_PARANOIA
- &input_plugin_cdio_paranoia,
-#endif
-#ifdef ENABLE_DESPOTIFY
- &input_plugin_despotify,
-#endif
- NULL
-};
-
-bool input_plugins_enabled[G_N_ELEMENTS(input_plugins) - 1];
diff --git a/src/input_registry.h b/src/input_registry.h
deleted file mode 100644
index 4f5fff8da..000000000
--- a/src/input_registry.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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_INPUT_REGISTRY_H
-#define MPD_INPUT_REGISTRY_H
-
-#include "check.h"
-
-#include <stdbool.h>
-
-/**
- * NULL terminated list of all input plugins which were enabled at
- * compile time.
- */
-extern const struct input_plugin *const input_plugins[];
-
-extern bool input_plugins_enabled[];
-
-#define input_plugins_for_each(plugin) \
- for (const struct input_plugin *plugin, \
- *const*input_plugin_iterator = &input_plugins[0]; \
- (plugin = *input_plugin_iterator) != NULL; \
- ++input_plugin_iterator)
-
-#define input_plugins_for_each_enabled(plugin) \
- input_plugins_for_each(plugin) \
- if (input_plugins_enabled[input_plugin_iterator - input_plugins])
-
-#endif
diff --git a/src/input_stream.c b/src/input_stream.c
deleted file mode 100644
index e445dca6c..000000000
--- a/src/input_stream.c
+++ /dev/null
@@ -1,243 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "input_stream.h"
-#include "input_registry.h"
-#include "input_plugin.h"
-#include "input/rewind_input_plugin.h"
-
-#include <glib.h>
-#include <assert.h>
-
-static inline GQuark
-input_quark(void)
-{
- return g_quark_from_static_string("input");
-}
-
-struct input_stream *
-input_stream_open(const char *url,
- GMutex *mutex, GCond *cond,
- GError **error_r)
-{
- GError *error = NULL;
-
- assert(mutex != NULL);
- assert(error_r == NULL || *error_r == NULL);
-
- input_plugins_for_each_enabled(plugin) {
- struct input_stream *is;
-
- is = plugin->open(url, mutex, cond, &error);
- if (is != NULL) {
- assert(is->plugin != NULL);
- assert(is->plugin->close != NULL);
- assert(is->plugin->read != NULL);
- assert(is->plugin->eof != NULL);
- assert(!is->seekable || is->plugin->seek != NULL);
-
- is = input_rewind_open(is);
-
- return is;
- } else if (error != NULL) {
- g_propagate_error(error_r, error);
- return NULL;
- }
- }
-
- g_set_error(error_r, input_quark(), 0, "Unrecognized URI");
- return NULL;
-}
-
-bool
-input_stream_check(struct input_stream *is, GError **error_r)
-{
- assert(is != NULL);
- assert(is->plugin != NULL);
-
- return is->plugin->check == NULL ||
- is->plugin->check(is, error_r);
-}
-
-void
-input_stream_update(struct input_stream *is)
-{
- assert(is != NULL);
- assert(is->plugin != NULL);
-
- if (is->plugin->update != NULL)
- is->plugin->update(is);
-}
-
-void
-input_stream_wait_ready(struct input_stream *is)
-{
- assert(is != NULL);
- assert(is->mutex != NULL);
- assert(is->cond != NULL);
-
- while (true) {
- input_stream_update(is);
- if (is->ready)
- break;
-
- g_cond_wait(is->cond, is->mutex);
- }
-}
-
-void
-input_stream_lock_wait_ready(struct input_stream *is)
-{
- assert(is != NULL);
- assert(is->mutex != NULL);
- assert(is->cond != NULL);
-
- g_mutex_lock(is->mutex);
- input_stream_wait_ready(is);
- g_mutex_unlock(is->mutex);
-}
-
-bool
-input_stream_seek(struct input_stream *is, goffset offset, int whence,
- GError **error_r)
-{
- assert(is != NULL);
- assert(is->plugin != NULL);
-
- if (is->plugin->seek == NULL)
- return false;
-
- return is->plugin->seek(is, offset, whence, error_r);
-}
-
-bool
-input_stream_lock_seek(struct input_stream *is, goffset offset, int whence,
- GError **error_r)
-{
- assert(is != NULL);
- assert(is->plugin != NULL);
-
- if (is->plugin->seek == NULL)
- return false;
-
- if (is->mutex == NULL)
- /* no locking */
- return input_stream_seek(is, offset, whence, error_r);
-
- g_mutex_lock(is->mutex);
- bool success = input_stream_seek(is, offset, whence, error_r);
- g_mutex_unlock(is->mutex);
- return success;
-}
-
-struct tag *
-input_stream_tag(struct input_stream *is)
-{
- assert(is != NULL);
- assert(is->plugin != NULL);
-
- return is->plugin->tag != NULL
- ? is->plugin->tag(is)
- : NULL;
-}
-
-struct tag *
-input_stream_lock_tag(struct input_stream *is)
-{
- assert(is != NULL);
- assert(is->plugin != NULL);
-
- if (is->plugin->tag == NULL)
- return false;
-
- if (is->mutex == NULL)
- /* no locking */
- return input_stream_tag(is);
-
- g_mutex_lock(is->mutex);
- struct tag *tag = input_stream_tag(is);
- g_mutex_unlock(is->mutex);
- return tag;
-}
-
-bool
-input_stream_available(struct input_stream *is)
-{
- assert(is != NULL);
- assert(is->plugin != NULL);
-
- return is->plugin->available != NULL
- ? is->plugin->available(is)
- : true;
-}
-
-size_t
-input_stream_read(struct input_stream *is, void *ptr, size_t size,
- GError **error_r)
-{
- assert(ptr != NULL);
- assert(size > 0);
-
- return is->plugin->read(is, ptr, size, error_r);
-}
-
-size_t
-input_stream_lock_read(struct input_stream *is, void *ptr, size_t size,
- GError **error_r)
-{
- assert(ptr != NULL);
- assert(size > 0);
-
- if (is->mutex == NULL)
- /* no locking */
- return input_stream_read(is, ptr, size, error_r);
-
- g_mutex_lock(is->mutex);
- size_t nbytes = input_stream_read(is, ptr, size, error_r);
- g_mutex_unlock(is->mutex);
- return nbytes;
-}
-
-void input_stream_close(struct input_stream *is)
-{
- is->plugin->close(is);
-}
-
-bool input_stream_eof(struct input_stream *is)
-{
- return is->plugin->eof(is);
-}
-
-bool
-input_stream_lock_eof(struct input_stream *is)
-{
- assert(is != NULL);
- assert(is->plugin != NULL);
-
- if (is->mutex == NULL)
- /* no locking */
- return input_stream_eof(is);
-
- g_mutex_lock(is->mutex);
- bool eof = input_stream_eof(is);
- g_mutex_unlock(is->mutex);
- return eof;
-}
-
diff --git a/src/input_stream.h b/src/input_stream.h
index 10ad97161..b5b251f59 100644
--- a/src/input_stream.h
+++ b/src/input_stream.h
@@ -29,64 +29,14 @@
#include <stdbool.h>
#include <sys/types.h>
-struct input_stream {
- /**
- * the plugin which implements this input stream
- */
- const struct input_plugin *plugin;
+struct Tag;
+struct input_stream;
- /**
- * The absolute URI which was used to open this stream. May
- * be NULL if this is unknown.
- */
- char *uri;
+#ifdef __cplusplus
+extern "C" {
- /**
- * A mutex that protects the mutable attributes of this object
- * and its implementation. It must be locked before calling
- * any of the public methods.
- *
- * This object is allocated by the client, and the client is
- * responsible for freeing it.
- */
- GMutex *mutex;
-
- /**
- * A cond that gets signalled when the state of this object
- * changes from the I/O thread. The client of this object may
- * wait on it. Optional, may be NULL.
- *
- * This object is allocated by the client, and the client is
- * responsible for freeing it.
- */
- GCond *cond;
-
- /**
- * indicates whether the stream is ready for reading and
- * whether the other attributes in this struct are valid
- */
- bool ready;
-
- /**
- * if true, then the stream is fully seekable
- */
- bool seekable;
-
- /**
- * the size of the resource, or -1 if unknown
- */
- goffset size;
-
- /**
- * the current offset within the stream
- */
- goffset offset;
-
- /**
- * the MIME content type of the resource, or NULL if unknown
- */
- char *mime;
-};
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
/**
* Opens a new input stream. You may not access it until the "ready"
@@ -99,13 +49,15 @@ struct input_stream {
* notifications
* @return an #input_stream object on success, NULL on error
*/
-gcc_nonnull(1, 2)
-G_GNUC_MALLOC
+gcc_nonnull(1)
+gcc_malloc
struct input_stream *
input_stream_open(const char *uri,
- GMutex *mutex, GCond *cond,
+ Mutex &mutex, Cond &cond,
GError **error_r);
+#endif
+
/**
* Close the input stream and free resources.
*
@@ -115,20 +67,6 @@ gcc_nonnull(1)
void
input_stream_close(struct input_stream *is);
-gcc_nonnull(1)
-static inline void
-input_stream_lock(struct input_stream *is)
-{
- g_mutex_lock(is->mutex);
-}
-
-gcc_nonnull(1)
-static inline void
-input_stream_unlock(struct input_stream *is)
-{
- g_mutex_unlock(is->mutex);
-}
-
/**
* Check for errors that may have occurred in the I/O thread.
*
@@ -163,6 +101,33 @@ gcc_nonnull(1)
void
input_stream_lock_wait_ready(struct input_stream *is);
+gcc_nonnull_all gcc_pure
+const char *
+input_stream_get_mime_type(const struct input_stream *is);
+
+gcc_nonnull_all
+void
+input_stream_override_mime_type(struct input_stream *is, const char *mime);
+
+gcc_nonnull_all gcc_pure
+goffset
+input_stream_get_size(const struct input_stream *is);
+
+gcc_nonnull_all gcc_pure
+goffset
+input_stream_get_offset(const struct input_stream *is);
+
+gcc_nonnull_all gcc_pure
+bool
+input_stream_is_seekable(const struct input_stream *is);
+
+/**
+ * Determines whether seeking is cheap. This is true for local files.
+ */
+gcc_pure gcc_nonnull(1)
+bool
+input_stream_cheap_seeking(const struct input_stream *is);
+
/**
* Seeks to the specified position in the stream. This will most
* likely fail if the "seekable" flag is false.
@@ -193,7 +158,7 @@ input_stream_lock_seek(struct input_stream *is, goffset offset, int whence,
* The caller must lock the mutex.
*/
gcc_nonnull(1)
-G_GNUC_PURE
+gcc_pure
bool input_stream_eof(struct input_stream *is);
/**
@@ -201,7 +166,7 @@ bool input_stream_eof(struct input_stream *is);
* the caller must not be holding it already.
*/
gcc_nonnull(1)
-G_GNUC_PURE
+gcc_pure
bool
input_stream_lock_eof(struct input_stream *is);
@@ -210,12 +175,12 @@ input_stream_lock_eof(struct input_stream *is);
*
* The caller must lock the mutex.
*
- * @return a tag object which must be freed with tag_free(), or NULL
+ * @return a tag object which must be freed by the caller, or nullptr
* if the tag has not changed since the last call
*/
gcc_nonnull(1)
-G_GNUC_MALLOC
-struct tag *
+gcc_malloc
+Tag *
input_stream_tag(struct input_stream *is);
/**
@@ -223,8 +188,8 @@ input_stream_tag(struct input_stream *is);
* mutex; the caller must not be holding it already.
*/
gcc_nonnull(1)
-G_GNUC_MALLOC
-struct tag *
+gcc_malloc
+Tag *
input_stream_lock_tag(struct input_stream *is);
/**
@@ -235,7 +200,7 @@ input_stream_lock_tag(struct input_stream *is);
* The caller must lock the mutex.
*/
gcc_nonnull(1)
-G_GNUC_PURE
+gcc_pure
bool
input_stream_available(struct input_stream *is);
@@ -264,4 +229,8 @@ size_t
input_stream_lock_read(struct input_stream *is, void *ptr, size_t size,
GError **error_r);
+#ifdef __cplusplus
+}
+#endif
+
#endif
diff --git a/src/io_error.h b/src/io_error.h
new file mode 100644
index 000000000..930ced108
--- /dev/null
+++ b/src/io_error.h
@@ -0,0 +1,51 @@
+/*
+ * 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_IO_ERROR_H
+#define MPD_IO_ERROR_H
+
+#include <glib.h>
+
+#include <errno.h>
+
+/**
+ * A GQuark for GError for I/O errors. The code is an errno value.
+ */
+G_GNUC_CONST
+static inline GQuark
+errno_quark(void)
+{
+ return g_quark_from_static_string("errno");
+}
+
+static inline void
+set_error_errno(GError **error_r)
+{
+ g_set_error_literal(error_r, errno_quark(), errno,
+ g_strerror(errno));
+}
+
+static inline GError *
+new_error_errno(void)
+{
+ return g_error_new_literal(errno_quark(), errno,
+ g_strerror(errno));
+}
+
+#endif
diff --git a/src/io_thread.c b/src/io_thread.c
deleted file mode 100644
index 7c080adcb..000000000
--- a/src/io_thread.c
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * 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.
- */
-
-#include "io_thread.h"
-
-#include <assert.h>
-
-static struct {
- GMutex *mutex;
- GCond *cond;
-
- GMainContext *context;
- GMainLoop *loop;
- GThread *thread;
-} io;
-
-void
-io_thread_run(void)
-{
- assert(io_thread_inside());
- assert(io.context != NULL);
- assert(io.loop != NULL);
-
- g_main_loop_run(io.loop);
-}
-
-static gpointer
-io_thread_func(G_GNUC_UNUSED gpointer arg)
-{
- /* lock+unlock to synchronize with io_thread_start(), to be
- sure that io.thread is set */
- g_mutex_lock(io.mutex);
- g_mutex_unlock(io.mutex);
-
- io_thread_run();
- return NULL;
-}
-
-void
-io_thread_init(void)
-{
- assert(io.context == NULL);
- assert(io.loop == NULL);
- assert(io.thread == NULL);
-
- io.mutex = g_mutex_new();
- io.cond = g_cond_new();
- io.context = g_main_context_new();
- io.loop = g_main_loop_new(io.context, false);
-}
-
-bool
-io_thread_start(GError **error_r)
-{
- assert(io.context != NULL);
- assert(io.loop != NULL);
- assert(io.thread == NULL);
-
- g_mutex_lock(io.mutex);
- io.thread = g_thread_create(io_thread_func, NULL, true, error_r);
- g_mutex_unlock(io.mutex);
- if (io.thread == NULL)
- return false;
-
- return true;
-}
-
-void
-io_thread_quit(void)
-{
- assert(io.loop != NULL);
-
- g_main_loop_quit(io.loop);
-}
-
-void
-io_thread_deinit(void)
-{
- if (io.thread != NULL) {
- io_thread_quit();
-
- g_thread_join(io.thread);
- }
-
- if (io.loop != NULL)
- g_main_loop_unref(io.loop);
-
- if (io.context != NULL)
- g_main_context_unref(io.context);
-
- g_cond_free(io.cond);
- g_mutex_free(io.mutex);
-}
-
-GMainContext *
-io_thread_context(void)
-{
- return io.context;
-}
-
-bool
-io_thread_inside(void)
-{
- return io.thread != NULL && g_thread_self() == io.thread;
-}
-
-guint
-io_thread_idle_add(GSourceFunc function, gpointer data)
-{
- GSource *source = g_idle_source_new();
- g_source_set_callback(source, function, data, NULL);
- guint id = g_source_attach(source, io.context);
- g_source_unref(source);
- return id;
-}
-
-GSource *
-io_thread_timeout_add(guint interval_ms, GSourceFunc function, gpointer data)
-{
- GSource *source = g_timeout_source_new(interval_ms);
- g_source_set_callback(source, function, data, NULL);
- g_source_attach(source, io.context);
- return source;
-}
-
-GSource *
-io_thread_timeout_add_seconds(guint interval,
- GSourceFunc function, gpointer data)
-{
- GSource *source = g_timeout_source_new_seconds(interval);
- g_source_set_callback(source, function, data, NULL);
- g_source_attach(source, io.context);
- return source;
-}
-
-struct call_data {
- GThreadFunc function;
- gpointer data;
- bool done;
- gpointer result;
-};
-
-static gboolean
-io_thread_call_func(gpointer _data)
-{
- struct call_data *data = _data;
-
- gpointer result = data->function(data->data);
-
- g_mutex_lock(io.mutex);
- data->done = true;
- data->result = result;
- g_cond_broadcast(io.cond);
- g_mutex_unlock(io.mutex);
-
- return false;
-}
-
-gpointer
-io_thread_call(GThreadFunc function, gpointer _data)
-{
- assert(io.thread != NULL);
-
- if (io_thread_inside())
- /* we're already in the I/O thread - no
- synchronization needed */
- return function(_data);
-
- struct call_data data = {
- .function = function,
- .data = _data,
- .done = false,
- };
-
- io_thread_idle_add(io_thread_call_func, &data);
-
- g_mutex_lock(io.mutex);
- while (!data.done)
- g_cond_wait(io.cond, io.mutex);
- g_mutex_unlock(io.mutex);
-
- return data.result;
-}
diff --git a/src/io_thread.h b/src/io_thread.h
deleted file mode 100644
index 8ff5a71e5..000000000
--- a/src/io_thread.h
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * 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_IO_THREAD_H
-#define MPD_IO_THREAD_H
-
-#include <glib.h>
-#include <stdbool.h>
-
-void
-io_thread_init(void);
-
-bool
-io_thread_start(GError **error_r);
-
-/**
- * Run the I/O event loop synchronously in the current thread. This
- * can be called instead of io_thread_start(). For testing purposes
- * only.
- */
-void
-io_thread_run(void);
-
-/**
- * Ask the I/O thread to quit, but does not wait for it. Usually, you
- * don't need to call this function, because io_thread_deinit()
- * includes this.
- */
-void
-io_thread_quit(void);
-
-void
-io_thread_deinit(void);
-
-G_GNUC_PURE
-GMainContext *
-io_thread_context(void);
-
-/**
- * Is the current thread the I/O thread?
- */
-G_GNUC_PURE
-bool
-io_thread_inside(void);
-
-guint
-io_thread_idle_add(GSourceFunc function, gpointer data);
-
-G_GNUC_MALLOC
-GSource *
-io_thread_timeout_add(guint interval_ms, GSourceFunc function, gpointer data);
-
-G_GNUC_MALLOC
-GSource *
-io_thread_timeout_add_seconds(guint interval,
- GSourceFunc function, gpointer data);
-
-/**
- * Call a function synchronously in the I/O thread.
- */
-gpointer
-io_thread_call(GThreadFunc function, gpointer data);
-
-#endif
diff --git a/src/listen.c b/src/listen.c
deleted file mode 100644
index 90e13b9c1..000000000
--- a/src/listen.c
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "listen.h"
-#include "server_socket.h"
-#include "client.h"
-#include "conf.h"
-#include "main.h"
-
-#include <string.h>
-#include <assert.h>
-
-#ifdef ENABLE_SYSTEMD_DAEMON
-#include <systemd/sd-daemon.h>
-#endif
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "listen"
-
-#define DEFAULT_PORT 6600
-
-static struct server_socket *listen_socket;
-int listen_port;
-
-static void
-listen_callback(int fd, const struct sockaddr *address,
- size_t address_length, int uid, G_GNUC_UNUSED void *ctx)
-{
- client_new(global_player_control, fd, address, address_length, uid);
-}
-
-static bool
-listen_add_config_param(unsigned int port,
- const struct config_param *param,
- GError **error_r)
-{
- assert(param != NULL);
-
- if (0 == strcmp(param->value, "any")) {
- return server_socket_add_port(listen_socket, port, error_r);
- } else if (param->value[0] == '/') {
- return server_socket_add_path(listen_socket, param->value,
- error_r);
- } else {
- return server_socket_add_host(listen_socket, param->value,
- port, error_r);
- }
-}
-
-static bool
-listen_systemd_activation(GError **error_r)
-{
-#ifdef ENABLE_SYSTEMD_DAEMON
- int n = sd_listen_fds(true);
- if (n <= 0) {
- if (n < 0)
- g_warning("sd_listen_fds() failed: %s",
- g_strerror(-n));
- return false;
- }
-
- for (int i = SD_LISTEN_FDS_START, end = SD_LISTEN_FDS_START + n;
- i != end; ++i)
- if (!server_socket_add_fd(listen_socket, i, error_r))
- return false;
-
- return true;
-#else
- (void)error_r;
- return false;
-#endif
-}
-
-bool
-listen_global_init(GError **error_r)
-{
- int port = config_get_positive(CONF_PORT, DEFAULT_PORT);
- const struct config_param *param =
- config_get_next_param(CONF_BIND_TO_ADDRESS, NULL);
- bool success;
- GError *error = NULL;
-
- listen_socket = server_socket_new(listen_callback, NULL);
-
- if (listen_systemd_activation(&error))
- return true;
-
- if (error != NULL) {
- g_propagate_error(error_r, error);
- return false;
- }
-
- if (param != NULL) {
- /* "bind_to_address" is configured, create listeners
- for all values */
-
- do {
- success = listen_add_config_param(port, param, &error);
- if (!success) {
- g_propagate_prefixed_error(error_r, error,
- "Failed to listen on %s (line %i): ",
- param->value, param->line);
- return false;
- }
-
- param = config_get_next_param(CONF_BIND_TO_ADDRESS,
- param);
- } while (param != NULL);
- } else {
- /* no "bind_to_address" configured, bind the
- configured port on all interfaces */
-
- success = server_socket_add_port(listen_socket, port, error_r);
- if (!success) {
- g_propagate_prefixed_error(error_r, error,
- "Failed to listen on *:%d: ",
- port);
- return false;
- }
- }
-
- if (!server_socket_open(listen_socket, error_r))
- return false;
-
- listen_port = port;
- return true;
-}
-
-void listen_global_finish(void)
-{
- g_debug("listen_global_finish called");
-
- assert(listen_socket != NULL);
-
- server_socket_free(listen_socket);
-}
diff --git a/src/listen.h b/src/listen.h
deleted file mode 100644
index 246e83706..000000000
--- a/src/listen.h
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * 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_LISTEN_H
-#define MPD_LISTEN_H
-
-#include <glib.h>
-
-#include <stdbool.h>
-
-extern int listen_port;
-
-bool
-listen_global_init(GError **error_r);
-
-void listen_global_finish(void);
-
-#endif
diff --git a/src/locate.c b/src/locate.c
deleted file mode 100644
index c9684d2b6..000000000
--- a/src/locate.c
+++ /dev/null
@@ -1,239 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "locate.h"
-#include "path.h"
-#include "tag.h"
-#include "song.h"
-
-#include <glib.h>
-
-#include <stdlib.h>
-
-#define LOCATE_TAG_FILE_KEY "file"
-#define LOCATE_TAG_FILE_KEY_OLD "filename"
-#define LOCATE_TAG_ANY_KEY "any"
-
-int
-locate_parse_type(const char *str)
-{
- if (0 == g_ascii_strcasecmp(str, LOCATE_TAG_FILE_KEY) ||
- 0 == g_ascii_strcasecmp(str, LOCATE_TAG_FILE_KEY_OLD))
- return LOCATE_TAG_FILE_TYPE;
-
- if (0 == g_ascii_strcasecmp(str, LOCATE_TAG_ANY_KEY))
- return LOCATE_TAG_ANY_TYPE;
-
- enum tag_type i = tag_name_parse_i(str);
- if (i != TAG_NUM_OF_ITEM_TYPES)
- return i;
-
- return -1;
-}
-
-static bool
-locate_item_init(struct locate_item *item,
- const char *type_string, const char *needle)
-{
- item->tag = locate_parse_type(type_string);
-
- if (item->tag < 0)
- return false;
-
- item->needle = g_strdup(needle);
-
- return true;
-}
-
-void
-locate_item_list_free(struct locate_item_list *list)
-{
- for (unsigned i = 0; i < list->length; ++i)
- g_free(list->items[i].needle);
-
- g_free(list);
-}
-
-struct locate_item_list *
-locate_item_list_new(unsigned length)
-{
- struct locate_item_list *list =
- g_malloc0(sizeof(*list) - sizeof(list->items[0]) +
- length * sizeof(list->items[0]));
- list->length = length;
-
- return list;
-}
-
-struct locate_item_list *
-locate_item_list_parse(char *argv[], int argc)
-{
- if (argc % 2 != 0)
- return NULL;
-
- struct locate_item_list *list = locate_item_list_new(argc / 2);
-
- for (unsigned i = 0; i < list->length; ++i) {
- if (!locate_item_init(&list->items[i], argv[i * 2],
- argv[i * 2 + 1])) {
- locate_item_list_free(list);
- return NULL;
- }
- }
-
- return list;
-}
-
-struct locate_item_list *
-locate_item_list_casefold(const struct locate_item_list *list)
-{
- struct locate_item_list *new_list = locate_item_list_new(list->length);
-
- for (unsigned i = 0; i < list->length; i++){
- new_list->items[i].needle =
- g_utf8_casefold(list->items[i].needle, -1);
- new_list->items[i].tag = list->items[i].tag;
- }
-
- return new_list;
-}
-
-void
-locate_item_free(struct locate_item *item)
-{
- g_free(item->needle);
- g_free(item);
-}
-
-static bool
-locate_tag_search(const struct song *song, enum tag_type type, const char *str)
-{
- bool ret = false;
-
- if (type == LOCATE_TAG_FILE_TYPE || (int)type == LOCATE_TAG_ANY_TYPE) {
- char *uri = song_get_uri(song);
- char *p = g_utf8_casefold(uri, -1);
- g_free(uri);
-
- if (strstr(p, str))
- ret = true;
- g_free(p);
- if (ret == 1 || type == LOCATE_TAG_FILE_TYPE)
- return ret;
- }
-
- if (!song->tag)
- return false;
-
- bool visited_types[TAG_NUM_OF_ITEM_TYPES];
- memset(visited_types, 0, sizeof(visited_types));
-
- for (unsigned i = 0; i < song->tag->num_items && !ret; i++) {
- visited_types[song->tag->items[i]->type] = true;
- if ((int)type != LOCATE_TAG_ANY_TYPE &&
- song->tag->items[i]->type != type) {
- continue;
- }
-
- char *duplicate = g_utf8_casefold(song->tag->items[i]->value, -1);
- if (*str && strstr(duplicate, str))
- ret = true;
- g_free(duplicate);
- }
-
- /** If the search critieron was not visited during the sweep
- * through the song's tag, it means this field is absent from
- * the tag or empty. Thus, if the searched string is also
- * empty (first char is a \0), then it's a match as well and
- * we should return true.
- */
- if (!*str && !visited_types[type])
- return true;
-
- return ret;
-}
-
-bool
-locate_song_search(const struct song *song,
- const struct locate_item_list *criteria)
-{
- for (unsigned i = 0; i < criteria->length; i++)
- if (!locate_tag_search(song, criteria->items[i].tag,
- criteria->items[i].needle))
- return false;
-
- return true;
-}
-
-static bool
-locate_tag_match(const struct song *song, enum tag_type type, const char *str)
-{
- if (type == LOCATE_TAG_FILE_TYPE || (int)type == LOCATE_TAG_ANY_TYPE) {
- char *uri = song_get_uri(song);
- bool matches = strcmp(str, uri) == 0;
- g_free(uri);
-
- if (matches)
- return true;
-
- if (type == LOCATE_TAG_FILE_TYPE)
- return false;
- }
-
- if (!song->tag)
- return false;
-
- bool visited_types[TAG_NUM_OF_ITEM_TYPES];
- memset(visited_types, 0, sizeof(visited_types));
-
- for (unsigned i = 0; i < song->tag->num_items; i++) {
- visited_types[song->tag->items[i]->type] = true;
- if ((int)type != LOCATE_TAG_ANY_TYPE &&
- song->tag->items[i]->type != type) {
- continue;
- }
-
- if (0 == strcmp(str, song->tag->items[i]->value))
- return true;
- }
-
- /** If the search critieron was not visited during the sweep
- * through the song's tag, it means this field is absent from
- * the tag or empty. Thus, if the searched string is also
- * empty (first char is a \0), then it's a match as well and
- * we should return true.
- */
- if (!*str && !visited_types[type])
- return true;
-
- return false;
-}
-
-bool
-locate_song_match(const struct song *song,
- const struct locate_item_list *criteria)
-{
- for (unsigned i = 0; i < criteria->length; i++)
- if (!locate_tag_match(song, criteria->items[i].tag,
- criteria->items[i].needle))
- return false;
-
- return true;
-}
diff --git a/src/locate.h b/src/locate.h
deleted file mode 100644
index ec20ded24..000000000
--- a/src/locate.h
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * 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_LOCATE_H
-#define MPD_LOCATE_H
-
-#include "gcc.h"
-
-#include <stdint.h>
-#include <stdbool.h>
-
-#define LOCATE_TAG_FILE_TYPE TAG_NUM_OF_ITEM_TYPES+10
-#define LOCATE_TAG_ANY_TYPE TAG_NUM_OF_ITEM_TYPES+20
-
-struct song;
-
-/* struct used for search, find, list queries */
-struct locate_item {
- int8_t tag;
- /* what we are looking for */
- char *needle;
-};
-
-/**
- * An array of struct locate_item objects.
- */
-struct locate_item_list {
- /** number of items */
- unsigned length;
-
- /** this is a variable length array */
- struct locate_item items[1];
-};
-
-int
-locate_parse_type(const char *str);
-
-/**
- * Allocates a new struct locate_item_list, and initializes all
- * members with zero bytes.
- */
-struct locate_item_list *
-locate_item_list_new(unsigned length);
-
-/* return number of items or -1 on error */
-gcc_nonnull(1)
-struct locate_item_list *
-locate_item_list_parse(char *argv[], int argc);
-
-/**
- * Duplicate the struct locate_item_list object and convert all
- * needles with g_utf8_casefold().
- */
-gcc_nonnull(1)
-struct locate_item_list *
-locate_item_list_casefold(const struct locate_item_list *list);
-
-gcc_nonnull(1)
-void
-locate_item_list_free(struct locate_item_list *list);
-
-gcc_nonnull(1)
-void
-locate_item_free(struct locate_item *item);
-
-gcc_nonnull(1,2)
-bool
-locate_song_search(const struct song *song,
- const struct locate_item_list *criteria);
-
-gcc_nonnull(1,2)
-bool
-locate_song_match(const struct song *song,
- const struct locate_item_list *criteria);
-
-#endif
diff --git a/src/log.c b/src/log.c
deleted file mode 100644
index 2d3c7cafd..000000000
--- a/src/log.c
+++ /dev/null
@@ -1,341 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "log.h"
-#include "conf.h"
-#include "utils.h"
-#include "fd_util.h"
-#include "mpd_error.h"
-
-#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
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "log"
-
-#define LOG_LEVEL_SECURE G_LOG_LEVEL_INFO
-
-#define LOG_DATE_BUF_SIZE 16
-#define LOG_DATE_LEN (LOG_DATE_BUF_SIZE - 1)
-
-static GLogLevelFlags log_threshold = G_LOG_LEVEL_MESSAGE;
-
-static const char *log_charset;
-
-static bool stdout_mode = true;
-static int out_fd;
-static char *out_filename;
-
-static void redirect_logs(int fd)
-{
- assert(fd >= 0);
- if (dup2(fd, STDOUT_FILENO) < 0)
- MPD_ERROR("problems dup2 stdout : %s\n", strerror(errno));
- if (dup2(fd, STDERR_FILENO) < 0)
- MPD_ERROR("problems dup2 stderr : %s\n", strerror(errno));
-}
-
-static const char *log_date(void)
-{
- static char buf[LOG_DATE_BUF_SIZE];
- time_t t = time(NULL);
- 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 && g_ascii_isspace(p[length - 1]))
- --length;
-
- return (int)length;
-}
-
-static void
-file_log_func(const gchar *log_domain,
- GLogLevelFlags log_level,
- const gchar *message, G_GNUC_UNUSED gpointer user_data)
-{
- char *converted;
-
- if (log_level > log_threshold)
- return;
-
- if (log_charset != NULL) {
- converted = g_convert_with_fallback(message, -1,
- log_charset, "utf-8",
- NULL, NULL, NULL, NULL);
- if (converted != NULL)
- message = converted;
- } else
- converted = NULL;
-
- if (log_domain == NULL)
- log_domain = "";
-
- fprintf(stderr, "%s%s%s%.*s\n",
- stdout_mode ? "" : log_date(),
- log_domain, *log_domain == 0 ? "" : ": ",
- chomp_length(message), message);
-
- g_free(converted);
-}
-
-static void
-log_init_stdout(void)
-{
- g_log_set_default_handler(file_log_func, NULL);
-}
-
-static int
-open_log_file(void)
-{
- assert(out_filename != NULL);
-
- return open_cloexec(out_filename, O_CREAT | O_WRONLY | O_APPEND, 0666);
-}
-
-static bool
-log_init_file(unsigned line, GError **error_r)
-{
- assert(out_filename != NULL);
-
- out_fd = open_log_file();
- if (out_fd < 0) {
- g_set_error(error_r, log_quark(), errno,
- "failed to open log file \"%s\" (config line %u): %s",
- out_filename, line, g_strerror(errno));
- return false;
- }
-
- g_log_set_default_handler(file_log_func, NULL);
- 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 *log_domain,
- GLogLevelFlags log_level, const gchar *message,
- G_GNUC_UNUSED gpointer user_data)
-{
- if (stdout_mode) {
- /* fall back to the file log function during
- startup */
- file_log_func(log_domain, log_level,
- message, user_data);
- return;
- }
-
- if (log_level > log_threshold)
- return;
-
- if (log_domain == NULL)
- log_domain = "";
-
- syslog(glib_to_syslog_level(log_level), "%s%s%.*s",
- log_domain, *log_domain == 0 ? "" : ": ",
- chomp_length(message), message);
-}
-
-static void
-log_init_syslog(void)
-{
- assert(out_filename == NULL);
-
- openlog(PACKAGE, 0, LOG_DAEMON);
- g_log_set_default_handler(syslog_log_func, NULL);
-}
-
-#endif
-
-static inline GLogLevelFlags
-parse_log_level(const char *value, unsigned line)
-{
- if (0 == strcmp(value, "default"))
- return G_LOG_LEVEL_MESSAGE;
- if (0 == strcmp(value, "secure"))
- return LOG_LEVEL_SECURE;
- else if (0 == strcmp(value, "verbose"))
- return G_LOG_LEVEL_DEBUG;
- else {
- MPD_ERROR("unknown log level \"%s\" at line %u\n",
- value, line);
- return G_LOG_LEVEL_MESSAGE;
- }
-}
-
-void
-log_early_init(bool verbose)
-{
- if (verbose)
- log_threshold = G_LOG_LEVEL_DEBUG;
-
- log_init_stdout();
-}
-
-bool
-log_init(bool verbose, bool use_stdout, GError **error_r)
-{
- const struct config_param *param;
-
- g_get_charset(&log_charset);
-
- if (verbose)
- log_threshold = G_LOG_LEVEL_DEBUG;
- else if ((param = config_get_param(CONF_LOG_LEVEL)) != NULL)
- log_threshold = parse_log_level(param->value, param->line);
-
- if (use_stdout) {
- log_init_stdout();
- return true;
- } else {
- param = config_get_param(CONF_LOG_FILE);
- if (param == NULL) {
-#ifdef HAVE_SYSLOG
- /* no configuration: default to syslog (if
- available) */
- log_init_syslog();
- return true;
-#else
- g_set_error(error_r, log_quark(), 0,
- "config parameter \"%s\" not found",
- CONF_LOG_FILE);
- return false;
-#endif
-#ifdef HAVE_SYSLOG
- } else if (strcmp(param->value, "syslog") == 0) {
- log_init_syslog();
- return true;
-#endif
- } else {
- out_filename = config_dup_path(CONF_LOG_FILE, error_r);
- return out_filename != NULL &&
- log_init_file(param->line, error_r);
- }
- }
-}
-
-static void
-close_log_files(void)
-{
- if (stdout_mode)
- return;
-
-#ifdef HAVE_SYSLOG
- if (out_filename == NULL)
- closelog();
-#endif
-}
-
-void
-log_deinit(void)
-{
- close_log_files();
- g_free(out_filename);
-}
-
-
-void setup_log_output(bool use_stdout)
-{
- fflush(NULL);
- if (!use_stdout) {
-#ifndef WIN32
- if (out_filename == NULL)
- out_fd = open("/dev/null", O_WRONLY);
-#endif
-
- if (out_fd >= 0) {
- redirect_logs(out_fd);
- close(out_fd);
- }
-
- stdout_mode = false;
- log_charset = NULL;
- }
-}
-
-int cycle_log_files(void)
-{
- int fd;
-
- if (stdout_mode || out_filename == NULL)
- return 0;
- assert(out_filename);
-
- g_debug("Cycling log files...\n");
- close_log_files();
-
- fd = open_log_file();
- if (fd < 0) {
- g_warning("error re-opening log file: %s\n", out_filename);
- return -1;
- }
-
- redirect_logs(fd);
- g_debug("Done cycling log files\n");
- return 0;
-}
diff --git a/src/log.h b/src/log.h
deleted file mode 100644
index 683ff3e9f..000000000
--- a/src/log.h
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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_LOG_H
-#define MPD_LOG_H
-
-#include <glib.h>
-#include <stdbool.h>
-
-G_GNUC_CONST
-static inline GQuark
-log_quark(void)
-{
- return g_quark_from_static_string("log");
-}
-
-/**
- * Configure a logging destination for daemon startup, before the
- * configuration file is read. This allows the daemon to use the
- * logging library (and the command line verbose level) before it's
- * daemonized.
- *
- * @param verbose true when the program is started with --verbose
- */
-void
-log_early_init(bool verbose);
-
-bool
-log_init(bool verbose, bool use_stdout, GError **error_r);
-
-void
-log_deinit(void);
-
-void setup_log_output(bool use_stdout);
-
-int cycle_log_files(void);
-
-#endif /* LOG_H */
diff --git a/src/ls.c b/src/ls.c
deleted file mode 100644
index 310c2d7b6..000000000
--- a/src/ls.c
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "ls.h"
-#include "uri.h"
-#include "client.h"
-
-#include <assert.h>
-#include <string.h>
-
-
-/**
- * file:// is not included in remoteUrlPrefixes, the connection method
- * is detected at runtime and displayed as a urlhandler if the client is
- * connected by IPC socket.
- */
-static const char *remoteUrlPrefixes[] = {
-#if defined(ENABLE_CURL) || defined(ENABLE_SOUP)
- "http://",
-#endif
-#ifdef ENABLE_MMS
- "mms://",
- "mmsh://",
- "mmst://",
- "mmsu://",
-#endif
-#ifdef HAVE_FFMPEG
- "gopher://",
- "rtp://",
- "rtsp://",
- "rtmp://",
- "rtmpt://",
- "rtmps://",
-#endif
-#ifdef ENABLE_CDIO_PARANOIA
- "cdda://",
-#endif
-#ifdef ENABLE_DESPOTIFY
- "spt://",
-#endif
- NULL
-};
-
-void print_supported_uri_schemes_to_fp(FILE *fp)
-{
- const char **prefixes = remoteUrlPrefixes;
-
-#ifdef HAVE_UN
- fprintf(fp, " file://");
-#endif
- while (*prefixes) {
- fprintf(fp, " %s", *prefixes);
- prefixes++;
- }
- fprintf(fp,"\n");
-}
-
-void print_supported_uri_schemes(struct client *client)
-{
- const char **prefixes = remoteUrlPrefixes;
-
- while (*prefixes) {
- client_printf(client, "handler: %s\n", *prefixes);
- prefixes++;
- }
-}
-
-bool uri_supported_scheme(const char *uri)
-{
- const char **urlPrefixes = remoteUrlPrefixes;
-
- assert(uri_has_scheme(uri));
-
- while (*urlPrefixes) {
- if (g_str_has_prefix(uri, *urlPrefixes))
- return true;
- urlPrefixes++;
- }
-
- return false;
-}
diff --git a/src/ls.cxx b/src/ls.cxx
new file mode 100644
index 000000000..9a74f88b8
--- /dev/null
+++ b/src/ls.cxx
@@ -0,0 +1,100 @@
+/*
+ * 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 "ls.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
+ * is detected at runtime and displayed as a urlhandler if the client is
+ * connected by IPC socket.
+ */
+static const char *remoteUrlPrefixes[] = {
+#if defined(ENABLE_CURL)
+ "http://",
+#endif
+#ifdef ENABLE_MMS
+ "mms://",
+ "mmsh://",
+ "mmst://",
+ "mmsu://",
+#endif
+#ifdef HAVE_FFMPEG
+ "gopher://",
+ "rtp://",
+ "rtsp://",
+ "rtmp://",
+ "rtmpt://",
+ "rtmps://",
+#endif
+#ifdef ENABLE_CDIO_PARANOIA
+ "cdda://",
+#endif
+#ifdef ENABLE_DESPOTIFY
+ "spt://",
+#endif
+ NULL
+};
+
+void print_supported_uri_schemes_to_fp(FILE *fp)
+{
+ const char **prefixes = remoteUrlPrefixes;
+
+#ifdef HAVE_UN
+ fprintf(fp, " file://");
+#endif
+ while (*prefixes) {
+ fprintf(fp, " %s", *prefixes);
+ prefixes++;
+ }
+ fprintf(fp,"\n");
+}
+
+void print_supported_uri_schemes(Client *client)
+{
+ const char **prefixes = remoteUrlPrefixes;
+
+ while (*prefixes) {
+ client_printf(client, "handler: %s\n", *prefixes);
+ prefixes++;
+ }
+}
+
+bool uri_supported_scheme(const char *uri)
+{
+ const char **urlPrefixes = remoteUrlPrefixes;
+
+ assert(uri_has_scheme(uri));
+
+ while (*urlPrefixes) {
+ if (g_str_has_prefix(uri, *urlPrefixes))
+ return true;
+ urlPrefixes++;
+ }
+
+ return false;
+}
diff --git a/src/ls.h b/src/ls.h
deleted file mode 100644
index 15cb01160..000000000
--- a/src/ls.h
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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_LS_H
-#define MPD_LS_H
-
-#include <stdbool.h>
-#include <stdio.h>
-
-struct client;
-
-/**
- * Checks whether the scheme of the specified URI is supported by MPD.
- * It is not allowed to pass an URI without a scheme, check with
- * uri_has_scheme() first.
- */
-bool uri_supported_scheme(const char *url);
-
-/**
- * Send a list of supported URI schemes to the client. This is the
- * response to the "urlhandlers" command.
- */
-void print_supported_uri_schemes(struct client *client);
-
-/**
- * Send a list of supported URI schemes to a file pointer.
- */
-void print_supported_uri_schemes_to_fp(FILE *fp);
-
-#endif
diff --git a/src/ls.hxx b/src/ls.hxx
new file mode 100644
index 000000000..8ae5a58fd
--- /dev/null
+++ b/src/ls.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_LS_HXX
+#define MPD_LS_HXX
+
+#include <stdio.h>
+
+class Client;
+
+/**
+ * Checks whether the scheme of the specified URI is supported by MPD.
+ * It is not allowed to pass an URI without a scheme, check with
+ * uri_has_scheme() first.
+ */
+bool uri_supported_scheme(const char *url);
+
+/**
+ * Send a list of supported URI schemes to the client. This is the
+ * response to the "urlhandlers" command.
+ */
+void print_supported_uri_schemes(Client *client);
+
+/**
+ * Send a list of supported URI schemes to a file pointer.
+ */
+void print_supported_uri_schemes_to_fp(FILE *fp);
+
+#endif
diff --git a/src/main.c b/src/main.c
deleted file mode 100644
index 12f8d86f6..000000000
--- a/src/main.c
+++ /dev/null
@@ -1,541 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "main.h"
-#include "daemon.h"
-#include "io_thread.h"
-#include "client.h"
-#include "client_idle.h"
-#include "idle.h"
-#include "command.h"
-#include "playlist.h"
-#include "stored_playlist.h"
-#include "database.h"
-#include "update.h"
-#include "player_thread.h"
-#include "listen.h"
-#include "cmdline.h"
-#include "conf.h"
-#include "path.h"
-#include "mapper.h"
-#include "chunk.h"
-#include "player_control.h"
-#include "stats.h"
-#include "sig_handlers.h"
-#include "audio_config.h"
-#include "output_all.h"
-#include "volume.h"
-#include "log.h"
-#include "permission.h"
-#include "pcm_resample.h"
-#include "replay_gain_config.h"
-#include "decoder_list.h"
-#include "input_init.h"
-#include "playlist_list.h"
-#include "state_file.h"
-#include "tag.h"
-#include "dbUtils.h"
-#include "zeroconf.h"
-#include "event_pipe.h"
-#include "tag_pool.h"
-#include "mpd_error.h"
-
-#ifdef ENABLE_INOTIFY
-#include "inotify_update.h"
-#endif
-
-#ifdef ENABLE_SQLITE
-#include "sticker.h"
-#endif
-
-#ifdef ENABLE_ARCHIVE
-#include "archive_list.h"
-#endif
-
-#include <glib.h>
-
-#include <unistd.h>
-#include <stdlib.h>
-#include <errno.h>
-#include <string.h>
-
-#ifdef HAVE_LOCALE_H
-#include <locale.h>
-#endif
-
-#ifdef WIN32
-#include <winsock2.h>
-#include <ws2tcpip.h>
-#endif
-
-enum {
- DEFAULT_BUFFER_SIZE = 2048,
- DEFAULT_BUFFER_BEFORE_PLAY = 10,
-};
-
-GThread *main_task;
-GMainLoop *main_loop;
-
-GCond *main_cond;
-
-struct player_control *global_player_control;
-
-static bool
-glue_daemonize_init(const struct options *options, GError **error_r)
-{
- GError *error = NULL;
-
- char *pid_file = config_dup_path(CONF_PID_FILE, &error);
- if (pid_file == NULL && error != NULL) {
- g_propagate_error(error_r, error);
- return false;
- }
-
- daemonize_init(config_get_string(CONF_USER, NULL),
- config_get_string(CONF_GROUP, NULL),
- pid_file);
- g_free(pid_file);
-
- if (options->kill)
- daemonize_kill();
-
- return true;
-}
-
-static bool
-glue_mapper_init(GError **error_r)
-{
- GError *error = NULL;
- char *music_dir = config_dup_path(CONF_MUSIC_DIR, &error);
- if (music_dir == NULL && error != NULL) {
- g_propagate_error(error_r, error);
- return false;
- }
-
- char *playlist_dir = config_dup_path(CONF_PLAYLIST_DIR, &error);
- if (playlist_dir == NULL && error != NULL) {
- g_propagate_error(error_r, error);
- return false;
- }
-
- if (music_dir == NULL)
- music_dir = g_strdup(g_get_user_special_dir(G_USER_DIRECTORY_MUSIC));
-
- mapper_init(music_dir, playlist_dir);
-
- g_free(music_dir);
- g_free(playlist_dir);
- return true;
-}
-
-/**
- * Returns the database. If this function returns false, this has not
- * succeeded, and the caller should create the database after the
- * process has been daemonized.
- */
-static bool
-glue_db_init_and_load(void)
-{
- const struct config_param *path = config_get_param(CONF_DB_FILE);
-
- GError *error = NULL;
- bool ret;
-
- if (!mapper_has_music_directory()) {
- if (path != NULL)
- g_message("Found " CONF_DB_FILE " setting without "
- CONF_MUSIC_DIR " - disabling database");
- db_init(NULL, NULL);
- return true;
- }
-
- if (path == NULL)
- MPD_ERROR(CONF_DB_FILE " setting missing");
-
- if (!db_init(path, &error))
- MPD_ERROR("%s", error->message);
-
- ret = db_load(&error);
- if (!ret)
- MPD_ERROR("%s", error->message);
-
- /* run database update after daemonization? */
- return db_exists();
-}
-
-/**
- * Configure and initialize the sticker subsystem.
- */
-static void
-glue_sticker_init(void)
-{
-#ifdef ENABLE_SQLITE
- GError *error = NULL;
- char *sticker_file = config_dup_path(CONF_STICKER_FILE, &error);
- if (sticker_file == NULL && error != NULL)
- MPD_ERROR("%s", error->message);
-
- if (!sticker_global_init(sticker_file, &error))
- MPD_ERROR("%s", error->message);
-
- g_free(sticker_file);
-#endif
-}
-
-static bool
-glue_state_file_init(GError **error_r)
-{
- GError *error = NULL;
-
- char *path = config_dup_path(CONF_STATE_FILE, &error);
- if (path == NULL && error != NULL) {
- g_propagate_error(error_r, error);
- return false;
- }
-
- state_file_init(path, global_player_control);
- g_free(path);
-
- return true;
-}
-
-/**
- * Windows-only initialization of the Winsock2 library.
- */
-static void winsock_init(void)
-{
-#ifdef WIN32
- WSADATA sockinfo;
- int retval;
-
- retval = WSAStartup(MAKEWORD(2, 2), &sockinfo);
- if(retval != 0)
- {
- MPD_ERROR("Attempt to open Winsock2 failed; error code %d\n",
- retval);
- }
-
- if (LOBYTE(sockinfo.wVersion) != 2)
- {
- MPD_ERROR("We use Winsock2 but your version is either too new "
- "or old; please install Winsock 2.x\n");
- }
-#endif
-}
-
-/**
- * Initialize the decoder and player core, including the music pipe.
- */
-static void
-initialize_decoder_and_player(void)
-{
- const struct config_param *param;
- char *test;
- size_t buffer_size;
- float perc;
- unsigned buffered_chunks;
- unsigned buffered_before_play;
-
- param = config_get_param(CONF_AUDIO_BUFFER_SIZE);
- if (param != NULL) {
- long tmp = strtol(param->value, &test, 10);
- if (*test != '\0' || tmp <= 0 || tmp == LONG_MAX)
- MPD_ERROR("buffer size \"%s\" is not a positive integer, "
- "line %i\n", param->value, param->line);
- buffer_size = tmp;
- } else
- buffer_size = DEFAULT_BUFFER_SIZE;
-
- buffer_size *= 1024;
-
- buffered_chunks = buffer_size / CHUNK_SIZE;
-
- if (buffered_chunks >= 1 << 15)
- MPD_ERROR("buffer size \"%li\" is too big\n", (long)buffer_size);
-
- param = config_get_param(CONF_BUFFER_BEFORE_PLAY);
- if (param != NULL) {
- perc = strtod(param->value, &test);
- if (*test != '%' || perc < 0 || perc > 100) {
- MPD_ERROR("buffered before play \"%s\" is not a positive "
- "percentage and less than 100 percent, line %i",
- param->value, param->line);
- }
- } else
- perc = DEFAULT_BUFFER_BEFORE_PLAY;
-
- buffered_before_play = (perc / 100) * buffered_chunks;
- if (buffered_before_play > buffered_chunks)
- buffered_before_play = buffered_chunks;
-
- global_player_control = pc_new(buffered_chunks, buffered_before_play);
-}
-
-/**
- * event_pipe callback function for PIPE_EVENT_IDLE
- */
-static void
-idle_event_emitted(void)
-{
- /* send "idle" notificaions to all subscribed
- clients */
- unsigned flags = idle_get();
- if (flags != 0)
- client_manager_idle_add(flags);
-}
-
-/**
- * event_pipe callback function for PIPE_EVENT_SHUTDOWN
- */
-static void
-shutdown_event_emitted(void)
-{
- g_main_loop_quit(main_loop);
-}
-
-int main(int argc, char *argv[])
-{
-#ifdef WIN32
- return win32_main(argc, argv);
-#else
- return mpd_main(argc, argv);
-#endif
-}
-
-int mpd_main(int argc, char *argv[])
-{
- struct options options;
- clock_t start;
- bool create_db;
- GError *error = NULL;
- bool success;
-
- daemonize_close_stdin();
-
-#ifdef HAVE_LOCALE_H
- /* initialize locale */
- setlocale(LC_CTYPE,"");
-#endif
-
- g_set_application_name("Music Player Daemon");
-
- /* enable GLib's thread safety code */
- g_thread_init(NULL);
-
- io_thread_init();
- winsock_init();
- idle_init();
- tag_pool_init();
- config_global_init();
-
- success = parse_cmdline(argc, argv, &options, &error);
- if (!success) {
- g_warning("%s", error->message);
- g_error_free(error);
- return EXIT_FAILURE;
- }
-
- if (!glue_daemonize_init(&options, &error)) {
- g_warning("%s", error->message);
- g_error_free(error);
- return EXIT_FAILURE;
- }
-
- stats_global_init();
- tag_lib_init();
-
- if (!log_init(options.verbose, options.log_stderr, &error)) {
- g_warning("%s", error->message);
- g_error_free(error);
- return EXIT_FAILURE;
- }
-
- success = listen_global_init(&error);
- if (!success) {
- g_warning("%s", error->message);
- g_error_free(error);
- return EXIT_FAILURE;
- }
-
- daemonize_set_user();
-
- main_task = g_thread_self();
- main_loop = g_main_loop_new(NULL, FALSE);
- main_cond = g_cond_new();
-
- event_pipe_init();
- event_pipe_register(PIPE_EVENT_IDLE, idle_event_emitted);
- event_pipe_register(PIPE_EVENT_SHUTDOWN, shutdown_event_emitted);
-
- path_global_init();
-
- if (!glue_mapper_init(&error)) {
- g_warning("%s", error->message);
- g_error_free(error);
- return EXIT_FAILURE;
- }
-
- initPermissions();
- playlist_global_init();
- spl_global_init();
-#ifdef ENABLE_ARCHIVE
- archive_plugin_init_all();
-#endif
-
- if (!pcm_resample_global_init(&error)) {
- g_warning("%s", error->message);
- g_error_free(error);
- return EXIT_FAILURE;
- }
-
- decoder_plugin_init_all();
- update_global_init();
-
- create_db = !glue_db_init_and_load();
-
- glue_sticker_init();
-
- command_init();
- initialize_decoder_and_player();
- volume_init();
- initAudioConfig();
- audio_output_all_init(global_player_control);
- client_manager_init();
- replay_gain_global_init();
-
- if (!input_stream_global_init(&error)) {
- g_warning("%s", error->message);
- g_error_free(error);
- return EXIT_FAILURE;
- }
-
- playlist_list_global_init();
-
- daemonize(options.daemon);
-
- setup_log_output(options.log_stderr);
-
- initSigHandlers();
-
- if (!io_thread_start(&error)) {
- g_warning("%s", error->message);
- g_error_free(error);
- return EXIT_FAILURE;
- }
-
- initZeroconf();
-
- player_create(global_player_control);
-
- if (create_db) {
- /* the database failed to load: recreate the
- database */
- unsigned job = update_enqueue(NULL, true);
- if (job == 0)
- MPD_ERROR("directory update failed");
- }
-
- if (!glue_state_file_init(&error)) {
- g_warning("%s", error->message);
- g_error_free(error);
- return EXIT_FAILURE;
- }
-
- success = config_get_bool(CONF_AUTO_UPDATE, false);
-#ifdef ENABLE_INOTIFY
- if (success && mapper_has_music_directory())
- mpd_inotify_init(config_get_unsigned(CONF_AUTO_UPDATE_DEPTH,
- G_MAXUINT));
-#else
- if (success)
- g_warning("inotify: auto_update was disabled. enable during compilation phase");
-#endif
-
- config_global_check();
-
- /* enable all audio outputs (if not already done by
- playlist_state_restore() */
- pc_update_audio(global_player_control);
-
-#ifdef WIN32
- win32_app_started();
-#endif
-
- /* run the main loop */
- g_main_loop_run(main_loop);
-
-#ifdef WIN32
- win32_app_stopping();
-#endif
-
- /* cleanup */
-
- g_main_loop_unref(main_loop);
-
-#ifdef ENABLE_INOTIFY
- mpd_inotify_finish();
-#endif
-
- state_file_finish(global_player_control);
- pc_kill(global_player_control);
- finishZeroconf();
- client_manager_deinit();
- listen_global_finish();
- playlist_global_finish();
-
- start = clock();
- db_finish();
- g_debug("db_finish took %f seconds",
- ((float)(clock()-start))/CLOCKS_PER_SEC);
-
-#ifdef ENABLE_SQLITE
- sticker_global_finish();
-#endif
-
- g_cond_free(main_cond);
- event_pipe_deinit();
-
- playlist_list_global_finish();
- input_stream_global_finish();
- audio_output_all_finish();
- volume_finish();
- mapper_finish();
- path_global_finish();
- finishPermissions();
- pc_free(global_player_control);
- command_finish();
- update_global_finish();
- decoder_plugin_deinit_all();
-#ifdef ENABLE_ARCHIVE
- archive_plugin_deinit_all();
-#endif
- config_global_finish();
- tag_pool_deinit();
- idle_deinit();
- stats_global_finish();
- io_thread_deinit();
- daemonize_finish();
-#ifdef WIN32
- WSACleanup();
-#endif
-
- log_deinit();
- return EXIT_SUCCESS;
-}
diff --git a/src/main.h b/src/main.h
deleted file mode 100644
index 2a7d75910..000000000
--- a/src/main.h
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * 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 MAIN_H
-#define MAIN_H
-
-#include <glib.h>
-
-extern GThread *main_task;
-
-extern GMainLoop *main_loop;
-
-extern GCond *main_cond;
-
-extern struct player_control *global_player_control;
-
-/**
- * A entry point for application.
- * On non-Windows platforms this is called directly from main()
- * On Windows platform this is called from win32_main()
- * after doing some initialization.
- */
-int mpd_main(int argc, char *argv[]);
-
-#ifdef WIN32
-
-/**
- * If program is run as windows service performs nessesary initialization
- * and then calls mpd_main() with specified arguments.
- * If program is run as a regular application calls mpd_main() immediately.
- */
-int
-win32_main(int argc, char *argv[]);
-
-/**
- * When running as a service reports to service control manager
- * that our service is started.
- * When running as a console application enables console handler that will
- * trigger PIPE_EVENT_SHUTDOWN when user closes console window
- * or presses Ctrl+C.
- * This function should be called just before entering main loop.
- */
-void
-win32_app_started(void);
-
-/**
- * When running as a service reports to service control manager
- * that our service is about to stop.
- * When running as a console application enables console handler that will
- * catch all shutdown requests and ignore them.
- * This function should be called just after leaving main loop.
- */
-void
-win32_app_stopping(void);
-
-#endif
-
-#endif
diff --git a/src/main_win32.c b/src/main_win32.c
deleted file mode 100644
index aac7ad886..000000000
--- a/src/main_win32.c
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "main.h"
-
-#ifdef WIN32
-
-#include "mpd_error.h"
-#include "event_pipe.h"
-
-#include <glib.h>
-
-#include <windows.h>
-
-static int service_argc;
-static char **service_argv;
-static char service_name[] = "";
-static BOOL ignore_console_events;
-static SERVICE_STATUS_HANDLE service_handle;
-
-static void WINAPI
-service_main(DWORD argc, CHAR *argv[]);
-
-static SERVICE_TABLE_ENTRY service_registry[] = {
- {service_name, service_main},
- {NULL, NULL}
-};
-
-static void
-service_notify_status(DWORD status_code)
-{
- SERVICE_STATUS current_status;
-
- current_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
- current_status.dwControlsAccepted = status_code == SERVICE_START_PENDING
- ? 0
- : SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP;
-
- current_status.dwCurrentState = status_code;
- current_status.dwWin32ExitCode = NO_ERROR;
- current_status.dwCheckPoint = 0;
- current_status.dwWaitHint = 1000;
-
- SetServiceStatus(service_handle, &current_status);
-}
-
-static DWORD WINAPI
-service_dispatcher(G_GNUC_UNUSED DWORD control, G_GNUC_UNUSED DWORD event_type,
- G_GNUC_UNUSED void *event_data, G_GNUC_UNUSED void *context)
-{
- switch (control) {
- case SERVICE_CONTROL_SHUTDOWN:
- case SERVICE_CONTROL_STOP:
- event_pipe_emit(PIPE_EVENT_SHUTDOWN);
- return NO_ERROR;
- default:
- return NO_ERROR;
- }
-}
-
-static void WINAPI
-service_main(G_GNUC_UNUSED DWORD argc, G_GNUC_UNUSED CHAR *argv[])
-{
- DWORD error_code;
- gchar* error_message;
-
- service_handle =
- RegisterServiceCtrlHandlerEx(service_name,
- service_dispatcher, NULL);
-
- if (service_handle == 0) {
- error_code = GetLastError();
- error_message = g_win32_error_message(error_code);
- MPD_ERROR("RegisterServiceCtrlHandlerEx() failed: %s",
- error_message);
- }
-
- service_notify_status(SERVICE_START_PENDING);
- mpd_main(service_argc, service_argv);
- service_notify_status(SERVICE_STOPPED);
-}
-
-static BOOL WINAPI
-console_handler(DWORD event)
-{
- switch (event) {
- case CTRL_C_EVENT:
- case CTRL_CLOSE_EVENT:
- if (!ignore_console_events)
- event_pipe_emit(PIPE_EVENT_SHUTDOWN);
- return TRUE;
- default:
- return FALSE;
- }
-}
-
-int win32_main(int argc, char *argv[])
-{
- DWORD error_code;
- gchar* error_message;
-
- service_argc = argc;
- service_argv = argv;
-
- if (StartServiceCtrlDispatcher(service_registry))
- return 0; /* run as service successefully */
-
- error_code = GetLastError();
- if (error_code == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) {
- /* running as console app */
- SetConsoleTitle("Music Player Daemon");
- ignore_console_events = TRUE;
- SetConsoleCtrlHandler(console_handler, TRUE);
- return mpd_main(argc, argv);
- }
-
- error_message = g_win32_error_message(error_code);
- MPD_ERROR("StartServiceCtrlDispatcher() failed: %s", error_message);
-}
-
-void win32_app_started()
-{
- if (service_handle != 0)
- service_notify_status(SERVICE_RUNNING);
- else
- ignore_console_events = FALSE;
-}
-
-void win32_app_stopping()
-{
- if (service_handle != 0)
- service_notify_status(SERVICE_STOP_PENDING);
- else
- ignore_console_events = TRUE;
-}
-
-#endif
diff --git a/src/mapper.c b/src/mapper.c
deleted file mode 100644
index 7db74b1af..000000000
--- a/src/mapper.c
+++ /dev/null
@@ -1,275 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * Maps directory and song objects to file system paths.
- */
-
-#include "config.h"
-#include "mapper.h"
-#include "directory.h"
-#include "song.h"
-#include "path.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <errno.h>
-#include <dirent.h>
-
-/**
- * The absolute path of the music directory encoded in UTF-8.
- */
-static char *music_dir_utf8;
-static size_t music_dir_utf8_length;
-
-/**
- * The absolute path of the music directory encoded in the filesystem
- * character set.
- */
-static char *music_dir_fs;
-static size_t music_dir_fs_length;
-
-/**
- * The absolute path of the playlist directory encoded in the
- * filesystem character set.
- */
-static char *playlist_dir_fs;
-
-/**
- * Duplicate a string, chop all trailing slashes.
- */
-static char *
-strdup_chop_slash(const char *path_fs)
-{
- size_t length = strlen(path_fs);
-
- while (length > 0 && path_fs[length - 1] == G_DIR_SEPARATOR)
- --length;
-
- return g_strndup(path_fs, length);
-}
-
-static void
-check_directory(const char *path)
-{
- struct stat st;
- if (stat(path, &st) < 0) {
- g_warning("Failed to stat directory \"%s\": %s",
- path, g_strerror(errno));
- return;
- }
-
- if (!S_ISDIR(st.st_mode)) {
- g_warning("Not a directory: %s", path);
- return;
- }
-
-#ifndef WIN32
- char *x = g_build_filename(path, ".", NULL);
- if (stat(x, &st) < 0 && errno == EACCES)
- g_warning("No permission to traverse (\"execute\") directory: %s",
- path);
- g_free(x);
-#endif
-
- DIR *dir = opendir(path);
- if (dir != NULL)
- closedir(dir);
- else if (errno == EACCES)
- g_warning("No permission to read directory: %s", path);
-}
-
-static void
-mapper_set_music_dir(const char *path_utf8)
-{
- music_dir_utf8 = strdup_chop_slash(path_utf8);
- music_dir_utf8_length = strlen(music_dir_utf8);
-
- music_dir_fs = utf8_to_fs_charset(music_dir_utf8);
- check_directory(music_dir_fs);
- music_dir_fs_length = strlen(music_dir_fs);
-}
-
-static void
-mapper_set_playlist_dir(const char *path_utf8)
-{
- playlist_dir_fs = utf8_to_fs_charset(path_utf8);
- check_directory(playlist_dir_fs);
-}
-
-void mapper_init(const char *_music_dir, const char *_playlist_dir)
-{
- if (_music_dir != NULL)
- mapper_set_music_dir(_music_dir);
-
- if (_playlist_dir != NULL)
- mapper_set_playlist_dir(_playlist_dir);
-}
-
-void mapper_finish(void)
-{
- g_free(music_dir_utf8);
- g_free(music_dir_fs);
- g_free(playlist_dir_fs);
-}
-
-const char *
-mapper_get_music_directory_utf8(void)
-{
- return music_dir_utf8;
-}
-
-const char *
-mapper_get_music_directory_fs(void)
-{
- return music_dir_fs;
-}
-
-const char *
-map_to_relative_path(const char *path_utf8)
-{
- return music_dir_utf8 != NULL &&
- memcmp(path_utf8, music_dir_utf8,
- music_dir_utf8_length) == 0 &&
- G_IS_DIR_SEPARATOR(path_utf8[music_dir_utf8_length])
- ? path_utf8 + music_dir_utf8_length + 1
- : path_utf8;
-}
-
-char *
-map_uri_fs(const char *uri)
-{
- char *uri_fs, *path_fs;
-
- assert(uri != NULL);
- assert(*uri != '/');
-
- if (music_dir_fs == NULL)
- return NULL;
-
- uri_fs = utf8_to_fs_charset(uri);
- if (uri_fs == NULL)
- return NULL;
-
- path_fs = g_build_filename(music_dir_fs, uri_fs, NULL);
- g_free(uri_fs);
-
- return path_fs;
-}
-
-char *
-map_directory_fs(const struct directory *directory)
-{
- assert(music_dir_utf8 != NULL);
- assert(music_dir_fs != NULL);
-
- if (directory_is_root(directory))
- return g_strdup(music_dir_fs);
-
- return map_uri_fs(directory_get_path(directory));
-}
-
-char *
-map_directory_child_fs(const struct directory *directory, const char *name)
-{
- assert(music_dir_utf8 != NULL);
- assert(music_dir_fs != NULL);
-
- char *name_fs, *parent_fs, *path;
-
- /* check for invalid or unauthorized base names */
- if (*name == 0 || strchr(name, '/') != NULL ||
- strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
- return NULL;
-
- parent_fs = map_directory_fs(directory);
- if (parent_fs == NULL)
- return NULL;
-
- name_fs = utf8_to_fs_charset(name);
- if (name_fs == NULL) {
- g_free(parent_fs);
- return NULL;
- }
-
- path = g_build_filename(parent_fs, name_fs, NULL);
- g_free(parent_fs);
- g_free(name_fs);
-
- return path;
-}
-
-char *
-map_song_fs(const struct song *song)
-{
- assert(song_is_file(song));
-
- if (song_in_database(song))
- return map_directory_child_fs(song->parent, song->uri);
- else
- return utf8_to_fs_charset(song->uri);
-}
-
-char *
-map_fs_to_utf8(const char *path_fs)
-{
- if (music_dir_fs != NULL &&
- strncmp(path_fs, music_dir_fs, music_dir_fs_length) == 0 &&
- G_IS_DIR_SEPARATOR(path_fs[music_dir_fs_length]))
- /* remove musicDir prefix */
- path_fs += music_dir_fs_length + 1;
- else if (G_IS_DIR_SEPARATOR(path_fs[0]))
- /* not within musicDir */
- return NULL;
-
- while (path_fs[0] == G_DIR_SEPARATOR)
- ++path_fs;
-
- return fs_charset_to_utf8(path_fs);
-}
-
-const char *
-map_spl_path(void)
-{
- return playlist_dir_fs;
-}
-
-char *
-map_spl_utf8_to_fs(const char *name)
-{
- char *filename_utf8, *filename_fs, *path;
-
- if (playlist_dir_fs == NULL)
- return NULL;
-
- filename_utf8 = g_strconcat(name, PLAYLIST_FILE_SUFFIX, NULL);
- filename_fs = utf8_to_fs_charset(filename_utf8);
- g_free(filename_utf8);
- if (filename_fs == NULL)
- return NULL;
-
- path = g_build_filename(playlist_dir_fs, filename_fs, NULL);
- g_free(filename_fs);
-
- return path;
-}
diff --git a/src/mapper.h b/src/mapper.h
deleted file mode 100644
index d6184a175..000000000
--- a/src/mapper.h
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * Maps directory and song objects to file system paths.
- */
-
-#ifndef MPD_MAPPER_H
-#define MPD_MAPPER_H
-
-#include <glib.h>
-#include <stdbool.h>
-
-#define PLAYLIST_FILE_SUFFIX ".m3u"
-
-struct directory;
-struct song;
-
-void mapper_init(const char *_music_dir, const char *_playlist_dir);
-
-void mapper_finish(void);
-
-/**
- * Return the absolute path of the music directory encoded in UTF-8.
- */
-G_GNUC_CONST
-const char *
-mapper_get_music_directory_utf8(void);
-
-/**
- * Return the absolute path of the music directory encoded in the
- * filesystem character set.
- */
-G_GNUC_CONST
-const char *
-mapper_get_music_directory_fs(void);
-
-/**
- * Returns true if a music directory was configured.
- */
-G_GNUC_CONST
-static inline bool
-mapper_has_music_directory(void)
-{
- return mapper_get_music_directory_utf8() != NULL;
-}
-
-/**
- * If the specified absolute path points inside the music directory,
- * this function converts it to a relative path. If not, it returns
- * the unmodified string pointer.
- */
-G_GNUC_PURE
-const char *
-map_to_relative_path(const char *path_utf8);
-
-/**
- * Determines the absolute file system path of a relative URI. This
- * is basically done by converting the URI to the file system charset
- * and prepending the music directory.
- */
-G_GNUC_MALLOC
-char *
-map_uri_fs(const char *uri);
-
-/**
- * Determines the file system path of a directory object.
- *
- * @param directory the directory object
- * @return the path in file system encoding, or NULL if mapping failed
- */
-G_GNUC_MALLOC
-char *
-map_directory_fs(const struct directory *directory);
-
-/**
- * Determines the file system path of a directory's child (may be a
- * sub directory or a song).
- *
- * @param directory the parent directory object
- * @param name the child's name in UTF-8
- * @return the path in file system encoding, or NULL if mapping failed
- */
-G_GNUC_MALLOC
-char *
-map_directory_child_fs(const struct directory *directory, const char *name);
-
-/**
- * Determines the file system path of a song. This must not be a
- * remote song.
- *
- * @param song the song object
- * @return the path in file system encoding, or NULL if mapping failed
- */
-G_GNUC_MALLOC
-char *
-map_song_fs(const struct song *song);
-
-/**
- * Maps a file system path (relative to the music directory or
- * absolute) to a relative path in UTF-8 encoding.
- *
- * @param path_fs a path in file system encoding
- * @return the relative path in UTF-8, or NULL if mapping failed
- */
-G_GNUC_MALLOC
-char *
-map_fs_to_utf8(const char *path_fs);
-
-/**
- * Returns the playlist directory.
- */
-G_GNUC_CONST
-const char *
-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().
- *
- * @return the path in file system encoding, or NULL if mapping failed
- */
-G_GNUC_PURE
-char *
-map_spl_utf8_to_fs(const char *name);
-
-#endif
diff --git a/src/mixer/AlsaMixerPlugin.cxx b/src/mixer/AlsaMixerPlugin.cxx
new file mode 100644
index 000000000..31e9997e3
--- /dev/null
+++ b/src/mixer/AlsaMixerPlugin.cxx
@@ -0,0 +1,370 @@
+/*
+ * 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 "MixerInternal.hxx"
+#include "OutputAPI.hxx"
+#include "GlobalEvents.hxx"
+#include "Main.hxx"
+#include "event/MultiSocketMonitor.hxx"
+
+#include <algorithm>
+
+#include <glib.h>
+#include <alsa/asoundlib.h>
+
+#define VOLUME_MIXER_ALSA_DEFAULT "default"
+#define VOLUME_MIXER_ALSA_CONTROL_DEFAULT "PCM"
+static constexpr unsigned VOLUME_MIXER_ALSA_INDEX_DEFAULT = 0;
+
+class AlsaMixerMonitor final : private MultiSocketMonitor {
+ snd_mixer_t *const mixer;
+
+public:
+ AlsaMixerMonitor(EventLoop &_loop, snd_mixer_t *_mixer)
+ :MultiSocketMonitor(_loop), mixer(_mixer) {}
+
+private:
+ virtual void PrepareSockets(gcc_unused gint *timeout_r) override;
+ virtual void DispatchSockets() override;
+};
+
+class AlsaMixer final : public Mixer {
+ const char *device;
+ const char *control;
+ unsigned int index;
+
+ snd_mixer_t *handle;
+ snd_mixer_elem_t *elem;
+ long volume_min;
+ long volume_max;
+ int volume_set;
+
+ AlsaMixerMonitor *monitor;
+
+public:
+ AlsaMixer():Mixer(alsa_mixer_plugin) {}
+
+ void Configure(const config_param &param);
+ bool Setup(GError **error_r);
+ bool Open(GError **error_r);
+ void Close();
+
+ int GetVolume(GError **error_r);
+ bool SetVolume(unsigned volume, GError **error_r);
+};
+
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+alsa_mixer_quark(void)
+{
+ return g_quark_from_static_string("alsa_mixer");
+}
+
+void
+AlsaMixerMonitor::PrepareSockets(gcc_unused gint *timeout_r)
+{
+ int count = snd_mixer_poll_descriptors_count(mixer);
+ if (count < 0)
+ count = 0;
+
+ struct pollfd *pfds = g_new(struct pollfd, count);
+ count = snd_mixer_poll_descriptors(mixer, pfds, count);
+ 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);
+
+ g_free(pfds);
+}
+
+void
+AlsaMixerMonitor::DispatchSockets()
+{
+ snd_mixer_handle_events(mixer);
+}
+
+/*
+ * libasound callbacks
+ *
+ */
+
+static int
+alsa_mixer_elem_callback(G_GNUC_UNUSED snd_mixer_elem_t *elem, unsigned mask)
+{
+ if (mask & SND_CTL_EVENT_MASK_VALUE)
+ GlobalEvents::Emit(GlobalEvents::MIXER);
+
+ return 0;
+}
+
+/*
+ * mixer_plugin methods
+ *
+ */
+
+inline void
+AlsaMixer::Configure(const config_param &param)
+{
+ device = param.GetBlockValue("mixer_device",
+ VOLUME_MIXER_ALSA_DEFAULT);
+ control = param.GetBlockValue("mixer_control",
+ VOLUME_MIXER_ALSA_CONTROL_DEFAULT);
+ index = param.GetBlockValue("mixer_index",
+ VOLUME_MIXER_ALSA_INDEX_DEFAULT);
+}
+
+static Mixer *
+alsa_mixer_init(G_GNUC_UNUSED void *ao, const config_param &param,
+ G_GNUC_UNUSED GError **error_r)
+{
+ AlsaMixer *am = new AlsaMixer();
+ am->Configure(param);
+
+ return am;
+}
+
+static void
+alsa_mixer_finish(Mixer *data)
+{
+ AlsaMixer *am = (AlsaMixer *)data;
+
+ delete am;
+
+ /* free libasound's config cache */
+ snd_config_update_free_global();
+}
+
+G_GNUC_PURE
+static snd_mixer_elem_t *
+alsa_mixer_lookup_elem(snd_mixer_t *handle, const char *name, unsigned idx)
+{
+ for (snd_mixer_elem_t *elem = snd_mixer_first_elem(handle);
+ elem != NULL; elem = snd_mixer_elem_next(elem)) {
+ if (snd_mixer_elem_get_type(elem) == SND_MIXER_ELEM_SIMPLE &&
+ g_ascii_strcasecmp(snd_mixer_selem_get_name(elem),
+ name) == 0 &&
+ snd_mixer_selem_get_index(elem) == idx)
+ return elem;
+ }
+
+ return NULL;
+}
+
+inline bool
+AlsaMixer::Setup(GError **error_r)
+{
+ int err;
+
+ if ((err = snd_mixer_attach(handle, device)) < 0) {
+ g_set_error(error_r, alsa_mixer_quark(), err,
+ "failed to attach to %s: %s",
+ device, snd_strerror(err));
+ return false;
+ }
+
+ if ((err = snd_mixer_selem_register(handle, NULL,
+ NULL)) < 0) {
+ g_set_error(error_r, alsa_mixer_quark(), err,
+ "snd_mixer_selem_register() failed: %s",
+ snd_strerror(err));
+ return false;
+ }
+
+ if ((err = snd_mixer_load(handle)) < 0) {
+ g_set_error(error_r, alsa_mixer_quark(), err,
+ "snd_mixer_load() failed: %s\n",
+ snd_strerror(err));
+ return false;
+ }
+
+ elem = alsa_mixer_lookup_elem(handle, control, index);
+ if (elem == NULL) {
+ g_set_error(error_r, alsa_mixer_quark(), 0,
+ "no such mixer control: %s", control);
+ return false;
+ }
+
+ snd_mixer_selem_get_playback_volume_range(elem, &volume_min,
+ &volume_max);
+
+ snd_mixer_elem_set_callback(elem, alsa_mixer_elem_callback);
+
+ monitor = new AlsaMixerMonitor(*main_loop, handle);
+
+ return true;
+}
+
+inline bool
+AlsaMixer::Open(GError **error_r)
+{
+ int err;
+
+ volume_set = -1;
+
+ err = snd_mixer_open(&handle, 0);
+ if (err < 0) {
+ g_set_error(error_r, alsa_mixer_quark(), err,
+ "snd_mixer_open() failed: %s", snd_strerror(err));
+ return false;
+ }
+
+ if (!Setup(error_r)) {
+ snd_mixer_close(handle);
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+alsa_mixer_open(Mixer *data, GError **error_r)
+{
+ AlsaMixer *am = (AlsaMixer *)data;
+
+ return am->Open(error_r);
+}
+
+inline void
+AlsaMixer::Close()
+{
+ assert(handle != NULL);
+
+ delete monitor;
+
+ snd_mixer_elem_set_callback(elem, NULL);
+ snd_mixer_close(handle);
+}
+
+static void
+alsa_mixer_close(Mixer *data)
+{
+ AlsaMixer *am = (AlsaMixer *)data;
+ am->Close();
+}
+
+inline int
+AlsaMixer::GetVolume(GError **error_r)
+{
+ int err;
+ int ret;
+ long level;
+
+ assert(handle != NULL);
+
+ err = snd_mixer_handle_events(handle);
+ if (err < 0) {
+ g_set_error(error_r, alsa_mixer_quark(), err,
+ "snd_mixer_handle_events() failed: %s",
+ snd_strerror(err));
+ return false;
+ }
+
+ err = snd_mixer_selem_get_playback_volume(elem,
+ SND_MIXER_SCHN_FRONT_LEFT,
+ &level);
+ if (err < 0) {
+ g_set_error(error_r, alsa_mixer_quark(), err,
+ "failed to read ALSA volume: %s",
+ snd_strerror(err));
+ return false;
+ }
+
+ ret = ((volume_set / 100.0) * (volume_max - volume_min)
+ + volume_min) + 0.5;
+ if (volume_set > 0 && ret == level) {
+ ret = volume_set;
+ } else {
+ ret = (int)(100 * (((float)(level - volume_min)) /
+ (volume_max - volume_min)) + 0.5);
+ }
+
+ return ret;
+}
+
+static int
+alsa_mixer_get_volume(Mixer *mixer, GError **error_r)
+{
+ AlsaMixer *am = (AlsaMixer *)mixer;
+ return am->GetVolume(error_r);
+}
+
+inline bool
+AlsaMixer::SetVolume(unsigned volume, GError **error_r)
+{
+ float vol;
+ long level;
+ int err;
+
+ assert(handle != NULL);
+
+ vol = volume;
+
+ volume_set = vol + 0.5;
+
+ 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;
+
+ err = snd_mixer_selem_set_playback_volume_all(elem, level);
+ if (err < 0) {
+ g_set_error(error_r, alsa_mixer_quark(), err,
+ "failed to set ALSA volume: %s",
+ snd_strerror(err));
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+alsa_mixer_set_volume(Mixer *mixer, unsigned volume, GError **error_r)
+{
+ AlsaMixer *am = (AlsaMixer *)mixer;
+ return am->SetVolume(volume, error_r);
+}
+
+const struct mixer_plugin alsa_mixer_plugin = {
+ alsa_mixer_init,
+ alsa_mixer_finish,
+ alsa_mixer_open,
+ alsa_mixer_close,
+ alsa_mixer_get_volume,
+ alsa_mixer_set_volume,
+ true,
+};
diff --git a/src/mixer/OssMixerPlugin.cxx b/src/mixer/OssMixerPlugin.cxx
new file mode 100644
index 000000000..bbb5b6c88
--- /dev/null
+++ b/src/mixer/OssMixerPlugin.cxx
@@ -0,0 +1,256 @@
+/*
+ * 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 "MixerInternal.hxx"
+#include "OutputAPI.hxx"
+#include "fd_util.h"
+
+#include <glib.h>
+
+#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>
+
+#if defined(__OpenBSD__) || defined(__NetBSD__)
+# include <soundcard.h>
+#else /* !(defined(__OpenBSD__) || defined(__NetBSD__) */
+# include <sys/soundcard.h>
+#endif /* !(defined(__OpenBSD__) || defined(__NetBSD__) */
+
+#define VOLUME_MIXER_OSS_DEFAULT "/dev/mixer"
+
+class OssMixer : public Mixer {
+ const char *device;
+ const char *control;
+
+ int device_fd;
+ int volume_control;
+
+public:
+ OssMixer():Mixer(oss_mixer_plugin) {}
+
+ bool Configure(const config_param &param, GError **error_r);
+ bool Open(GError **error_r);
+ void Close();
+
+ int GetVolume(GError **error_r);
+ bool SetVolume(unsigned volume, GError **error_r);
+};
+
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+oss_mixer_quark(void)
+{
+ return g_quark_from_static_string("oss_mixer");
+}
+
+static int
+oss_find_mixer(const char *name)
+{
+ const char *labels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS;
+ size_t name_length = strlen(name);
+
+ for (unsigned i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
+ if (g_ascii_strncasecmp(name, labels[i], name_length) == 0 &&
+ (labels[i][name_length] == 0 ||
+ labels[i][name_length] == ' '))
+ return i;
+ }
+ return -1;
+}
+
+inline bool
+OssMixer::Configure(const config_param &param, GError **error_r)
+{
+ device = param.GetBlockValue("mixer_device",
+ VOLUME_MIXER_OSS_DEFAULT);
+ control = param.GetBlockValue("mixer_control");
+
+ if (control != NULL) {
+ volume_control = oss_find_mixer(control);
+ if (volume_control < 0) {
+ g_set_error(error_r, oss_mixer_quark(), 0,
+ "no such mixer control: %s", control);
+ return false;
+ }
+ } else
+ volume_control = SOUND_MIXER_PCM;
+
+ return true;
+}
+
+static Mixer *
+oss_mixer_init(G_GNUC_UNUSED void *ao, const config_param &param,
+ GError **error_r)
+{
+ OssMixer *om = new OssMixer();
+
+ if (!om->Configure(param, error_r)) {
+ delete om;
+ return nullptr;
+ }
+
+ return om;
+}
+
+static void
+oss_mixer_finish(Mixer *data)
+{
+ OssMixer *om = (OssMixer *) data;
+
+ delete om;
+}
+
+void
+OssMixer::Close()
+{
+ assert(device_fd >= 0);
+
+ close(device_fd);
+}
+
+static void
+oss_mixer_close(Mixer *data)
+{
+ OssMixer *om = (OssMixer *) data;
+ om->Close();
+}
+
+inline bool
+OssMixer::Open(GError **error_r)
+{
+ device_fd = open_cloexec(device, O_RDONLY, 0);
+ if (device_fd < 0) {
+ g_set_error(error_r, oss_mixer_quark(), errno,
+ "failed to open %s: %s",
+ device, g_strerror(errno));
+ return false;
+ }
+
+ if (control) {
+ int devmask = 0;
+
+ if (ioctl(device_fd, SOUND_MIXER_READ_DEVMASK, &devmask) < 0) {
+ g_set_error(error_r, oss_mixer_quark(), errno,
+ "READ_DEVMASK failed: %s",
+ g_strerror(errno));
+ Close();
+ return false;
+ }
+
+ if (((1 << volume_control) & devmask) == 0) {
+ g_set_error(error_r, oss_mixer_quark(), 0,
+ "mixer control \"%s\" not usable",
+ control);
+ Close();
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool
+oss_mixer_open(Mixer *data, GError **error_r)
+{
+ OssMixer *om = (OssMixer *) data;
+
+ return om->Open(error_r);
+}
+
+inline int
+OssMixer::GetVolume(GError **error_r)
+{
+ int left, right, level;
+ int ret;
+
+ assert(device_fd >= 0);
+
+ ret = ioctl(device_fd, MIXER_READ(volume_control), &level);
+ if (ret < 0) {
+ g_set_error(error_r, oss_mixer_quark(), errno,
+ "failed to read OSS volume: %s",
+ g_strerror(errno));
+ return false;
+ }
+
+ left = level & 0xff;
+ right = (level & 0xff00) >> 8;
+
+ if (left != right) {
+ g_warning("volume for left and right is not the same, \"%i\" and "
+ "\"%i\"\n", left, right);
+ }
+
+ return left;
+}
+
+static int
+oss_mixer_get_volume(Mixer *mixer, GError **error_r)
+{
+ OssMixer *om = (OssMixer *)mixer;
+ return om->GetVolume(error_r);
+}
+
+inline bool
+OssMixer::SetVolume(unsigned volume, GError **error_r)
+{
+ int level;
+ int ret;
+
+ assert(device_fd >= 0);
+ assert(volume <= 100);
+
+ level = (volume << 8) + volume;
+
+ ret = ioctl(device_fd, MIXER_WRITE(volume_control), &level);
+ if (ret < 0) {
+ g_set_error(error_r, oss_mixer_quark(), errno,
+ "failed to set OSS volume: %s",
+ g_strerror(errno));
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+oss_mixer_set_volume(Mixer *mixer, unsigned volume, GError **error_r)
+{
+ OssMixer *om = (OssMixer *)mixer;
+ return om->SetVolume(volume, error_r);
+}
+
+const struct mixer_plugin oss_mixer_plugin = {
+ oss_mixer_init,
+ oss_mixer_finish,
+ oss_mixer_open,
+ oss_mixer_close,
+ oss_mixer_get_volume,
+ oss_mixer_set_volume,
+ true,
+};
diff --git a/src/mixer/PulseMixerPlugin.cxx b/src/mixer/PulseMixerPlugin.cxx
new file mode 100644
index 000000000..9cfd2dcf8
--- /dev/null
+++ b/src/mixer/PulseMixerPlugin.cxx
@@ -0,0 +1,235 @@
+/*
+ * 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 "PulseMixerPlugin.hxx"
+#include "MixerInternal.hxx"
+#include "output/PulseOutputPlugin.hxx"
+#include "conf.h"
+#include "GlobalEvents.hxx"
+
+#include <glib.h>
+
+#include <pulse/thread-mainloop.h>
+#include <pulse/context.h>
+#include <pulse/introspect.h>
+#include <pulse/stream.h>
+#include <pulse/subscribe.h>
+#include <pulse/error.h>
+
+#include <assert.h>
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "pulse_mixer"
+
+struct PulseMixer final : public Mixer {
+ PulseOutput *output;
+
+ bool online;
+ struct pa_cvolume volume;
+
+ PulseMixer(PulseOutput *_output)
+ :Mixer(pulse_mixer_plugin),
+ output(_output), online(false)
+ {
+ }
+};
+
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+pulse_mixer_quark(void)
+{
+ return g_quark_from_static_string("pulse_mixer");
+}
+
+static void
+pulse_mixer_offline(PulseMixer *pm)
+{
+ if (!pm->online)
+ return;
+
+ pm->online = false;
+
+ GlobalEvents::Emit(GlobalEvents::MIXER);
+}
+
+/**
+ * Callback invoked by pulse_mixer_update(). Receives the new mixer
+ * value.
+ */
+static void
+pulse_mixer_volume_cb(G_GNUC_UNUSED pa_context *context, const pa_sink_input_info *i,
+ int eol, void *userdata)
+{
+ PulseMixer *pm = (PulseMixer *)userdata;
+
+ if (eol)
+ return;
+
+ if (i == NULL) {
+ pulse_mixer_offline(pm);
+ return;
+ }
+
+ pm->online = true;
+ pm->volume = i->volume;
+
+ GlobalEvents::Emit(GlobalEvents::MIXER);
+}
+
+static void
+pulse_mixer_update(PulseMixer *pm,
+ struct pa_context *context, struct pa_stream *stream)
+{
+ pa_operation *o;
+
+ assert(context != NULL);
+ assert(stream != NULL);
+ assert(pa_stream_get_state(stream) == PA_STREAM_READY);
+
+ o = pa_context_get_sink_input_info(context,
+ pa_stream_get_index(stream),
+ pulse_mixer_volume_cb, pm);
+ if (o == NULL) {
+ g_warning("pa_context_get_sink_input_info() failed: %s",
+ pa_strerror(pa_context_errno(context)));
+ pulse_mixer_offline(pm);
+ return;
+ }
+
+ pa_operation_unref(o);
+}
+
+void
+pulse_mixer_on_connect(G_GNUC_UNUSED PulseMixer *pm,
+ struct pa_context *context)
+{
+ pa_operation *o;
+
+ assert(context != NULL);
+
+ o = pa_context_subscribe(context,
+ (pa_subscription_mask_t)PA_SUBSCRIPTION_MASK_SINK_INPUT,
+ NULL, NULL);
+ if (o == NULL) {
+ g_warning("pa_context_subscribe() failed: %s",
+ pa_strerror(pa_context_errno(context)));
+ return;
+ }
+
+ pa_operation_unref(o);
+}
+
+void
+pulse_mixer_on_disconnect(PulseMixer *pm)
+{
+ pulse_mixer_offline(pm);
+}
+
+void
+pulse_mixer_on_change(PulseMixer *pm,
+ struct pa_context *context, struct pa_stream *stream)
+{
+ pulse_mixer_update(pm, context, stream);
+}
+
+static Mixer *
+pulse_mixer_init(void *ao, gcc_unused const config_param &param,
+ GError **error_r)
+{
+ PulseOutput *po = (PulseOutput *)ao;
+
+ if (ao == NULL) {
+ g_set_error(error_r, pulse_mixer_quark(), 0,
+ "The pulse mixer cannot work without the audio output");
+ return nullptr;
+ }
+
+ PulseMixer *pm = new PulseMixer(po);
+
+ pulse_output_set_mixer(po, pm);
+
+ return pm;
+}
+
+static void
+pulse_mixer_finish(Mixer *data)
+{
+ PulseMixer *pm = (PulseMixer *) data;
+
+ pulse_output_clear_mixer(pm->output, pm);
+
+ delete pm;
+}
+
+static int
+pulse_mixer_get_volume(Mixer *mixer, G_GNUC_UNUSED GError **error_r)
+{
+ PulseMixer *pm = (PulseMixer *) mixer;
+ int ret;
+
+ pulse_output_lock(pm->output);
+
+ ret = pm->online
+ ? (int)((100*(pa_cvolume_avg(&pm->volume)+1))/PA_VOLUME_NORM)
+ : -1;
+
+ pulse_output_unlock(pm->output);
+
+ return ret;
+}
+
+static bool
+pulse_mixer_set_volume(Mixer *mixer, unsigned volume, GError **error_r)
+{
+ PulseMixer *pm = (PulseMixer *) mixer;
+ struct pa_cvolume cvolume;
+ bool success;
+
+ pulse_output_lock(pm->output);
+
+ if (!pm->online) {
+ pulse_output_unlock(pm->output);
+ g_set_error(error_r, pulse_mixer_quark(), 0, "disconnected");
+ return false;
+ }
+
+ pa_cvolume_set(&cvolume, pm->volume.channels,
+ (pa_volume_t)volume * PA_VOLUME_NORM / 100 + 0.5);
+ success = pulse_output_set_volume(pm->output, &cvolume, error_r);
+ if (success)
+ pm->volume = cvolume;
+
+ pulse_output_unlock(pm->output);
+
+ return success;
+}
+
+const struct mixer_plugin pulse_mixer_plugin = {
+ pulse_mixer_init,
+ pulse_mixer_finish,
+ nullptr,
+ nullptr,
+ pulse_mixer_get_volume,
+ pulse_mixer_set_volume,
+ false,
+};
diff --git a/src/mixer/PulseMixerPlugin.hxx b/src/mixer/PulseMixerPlugin.hxx
new file mode 100644
index 000000000..debc2cf3c
--- /dev/null
+++ b/src/mixer/PulseMixerPlugin.hxx
@@ -0,0 +1,47 @@
+/*
+ * 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_PULSE_MIXER_PLUGIN_HXX
+#define MPD_PULSE_MIXER_PLUGIN_HXX
+
+#include <pulse/def.h>
+
+struct PulseMixer;
+struct pa_context;
+struct pa_stream;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void
+pulse_mixer_on_connect(PulseMixer *pm, struct 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);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/mixer/RoarMixerPlugin.cxx b/src/mixer/RoarMixerPlugin.cxx
new file mode 100644
index 000000000..90d54ddaa
--- /dev/null
+++ b/src/mixer/RoarMixerPlugin.cxx
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2010-2011 Philipp 'ph3-der-loewe' Schafft
+ * Copyright (C) 2010-2011 Hans-Kristian 'maister' Arntzen
+ *
+ * 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 "MixerInternal.hxx"
+#include "OutputAPI.hxx"
+#include "output/RoarOutputPlugin.hxx"
+
+struct RoarMixer final : public Mixer {
+ /** the base mixer class */
+ RoarOutput *self;
+
+ RoarMixer(RoarOutput *_output)
+ :Mixer(roar_mixer_plugin),
+ self(_output) {}
+};
+
+static Mixer *
+roar_mixer_init(void *ao, gcc_unused const config_param &param,
+ gcc_unused GError **error_r)
+{
+ return new RoarMixer((RoarOutput *)ao);
+}
+
+static void
+roar_mixer_finish(Mixer *data)
+{
+ RoarMixer *self = (RoarMixer *) data;
+
+ delete self;
+}
+
+static int
+roar_mixer_get_volume(Mixer *mixer, gcc_unused GError **error_r)
+{
+ RoarMixer *self = (RoarMixer *)mixer;
+ return roar_output_get_volume(self->self);
+}
+
+static bool
+roar_mixer_set_volume(Mixer *mixer, unsigned volume,
+ gcc_unused GError **error_r)
+{
+ RoarMixer *self = (RoarMixer *)mixer;
+ return roar_output_set_volume(self->self, volume);
+}
+
+const struct mixer_plugin roar_mixer_plugin = {
+ roar_mixer_init,
+ roar_mixer_finish,
+ nullptr,
+ nullptr,
+ roar_mixer_get_volume,
+ roar_mixer_set_volume,
+ false,
+};
diff --git a/src/mixer/SoftwareMixerPlugin.cxx b/src/mixer/SoftwareMixerPlugin.cxx
new file mode 100644
index 000000000..8a268aaf1
--- /dev/null
+++ b/src/mixer/SoftwareMixerPlugin.cxx
@@ -0,0 +1,113 @@
+/*
+ * 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 "SoftwareMixerPlugin.hxx"
+#include "MixerInternal.hxx"
+#include "FilterPlugin.hxx"
+#include "FilterRegistry.hxx"
+#include "FilterInternal.hxx"
+#include "filter/VolumeFilterPlugin.hxx"
+#include "pcm/PcmVolume.hxx"
+#include "ConfigData.hxx"
+
+#include <assert.h>
+#include <math.h>
+
+struct SoftwareMixer final : public Mixer {
+ Filter *filter;
+
+ unsigned volume;
+
+ SoftwareMixer()
+ :Mixer(software_mixer_plugin),
+ filter(filter_new(&volume_filter_plugin, config_param(),
+ nullptr)),
+ volume(100)
+ {
+ assert(filter != nullptr);
+ }
+
+ ~SoftwareMixer() {
+ delete filter;
+ }
+};
+
+static Mixer *
+software_mixer_init(gcc_unused void *ao,
+ gcc_unused const config_param &param,
+ gcc_unused GError **error_r)
+{
+ return new SoftwareMixer();
+}
+
+static void
+software_mixer_finish(Mixer *data)
+{
+ SoftwareMixer *sm = (SoftwareMixer *)data;
+
+ delete sm;
+}
+
+static int
+software_mixer_get_volume(Mixer *mixer, gcc_unused GError **error_r)
+{
+ SoftwareMixer *sm = (SoftwareMixer *)mixer;
+
+ return sm->volume;
+}
+
+static bool
+software_mixer_set_volume(Mixer *mixer, unsigned volume,
+ gcc_unused GError **error_r)
+{
+ SoftwareMixer *sm = (SoftwareMixer *)mixer;
+
+ assert(volume <= 100);
+
+ sm->volume = volume;
+
+ if (volume >= 100)
+ volume = PCM_VOLUME_1;
+ else if (volume > 0)
+ volume = pcm_float_to_volume((exp(volume / 25.0) - 1) /
+ (54.5981500331F - 1));
+
+ volume_filter_set(sm->filter, volume);
+ return true;
+}
+
+const struct mixer_plugin software_mixer_plugin = {
+ software_mixer_init,
+ software_mixer_finish,
+ nullptr,
+ nullptr,
+ software_mixer_get_volume,
+ software_mixer_set_volume,
+ true,
+};
+
+Filter *
+software_mixer_get_filter(Mixer *mixer)
+{
+ SoftwareMixer *sm = (SoftwareMixer *)mixer;
+ assert(sm->IsPlugin(software_mixer_plugin));
+
+ return sm->filter;
+}
diff --git a/src/mixer/SoftwareMixerPlugin.hxx b/src/mixer/SoftwareMixerPlugin.hxx
new file mode 100644
index 000000000..be59c08db
--- /dev/null
+++ b/src/mixer/SoftwareMixerPlugin.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_SOFTWARE_MIXER_PLUGIN_HXX
+#define MPD_SOFTWARE_MIXER_PLUGIN_HXX
+
+class Mixer;
+class Filter;
+
+/**
+ * Returns the (volume) filter associated with this mixer. All users
+ * of this mixer plugin should install this filter.
+ */
+Filter *
+software_mixer_get_filter(Mixer *mixer);
+
+#endif
diff --git a/src/mixer/WinmmMixerPlugin.cxx b/src/mixer/WinmmMixerPlugin.cxx
new file mode 100644
index 000000000..139cb1399
--- /dev/null
+++ b/src/mixer/WinmmMixerPlugin.cxx
@@ -0,0 +1,121 @@
+/*
+ * 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 "MixerInternal.hxx"
+#include "OutputAPI.hxx"
+#include "output/WinmmOutputPlugin.hxx"
+
+#include <mmsystem.h>
+
+#include <assert.h>
+#include <math.h>
+#include <windows.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "winmm_mixer"
+
+struct WinmmMixer final : public Mixer {
+ WinmmOutput *output;
+
+ WinmmMixer(WinmmOutput *_output)
+ :Mixer(winmm_mixer_plugin),
+ output(_output) {
+ }
+};
+
+static inline GQuark
+winmm_mixer_quark(void)
+{
+ return g_quark_from_static_string("winmm_mixer");
+}
+
+static inline int
+winmm_volume_decode(DWORD volume)
+{
+ return lround((volume & 0xFFFF) / 655.35);
+}
+
+static inline DWORD
+winmm_volume_encode(int volume)
+{
+ int value = lround(volume * 655.35);
+ return MAKELONG(value, value);
+}
+
+static Mixer *
+winmm_mixer_init(void *ao, gcc_unused const config_param &param,
+ G_GNUC_UNUSED GError **error_r)
+{
+ assert(ao != nullptr);
+
+ return new WinmmMixer((WinmmOutput *)ao);
+}
+
+static void
+winmm_mixer_finish(Mixer *data)
+{
+ WinmmMixer *wm = (WinmmMixer *)data;
+
+ delete wm;
+}
+
+static int
+winmm_mixer_get_volume(Mixer *mixer, GError **error_r)
+{
+ WinmmMixer *wm = (WinmmMixer *) mixer;
+ DWORD volume;
+ HWAVEOUT handle = winmm_output_get_handle(wm->output);
+ MMRESULT result = waveOutGetVolume(handle, &volume);
+
+ if (result != MMSYSERR_NOERROR) {
+ g_set_error(error_r, 0, winmm_mixer_quark(),
+ "Failed to get winmm volume");
+ return -1;
+ }
+
+ return winmm_volume_decode(volume);
+}
+
+static bool
+winmm_mixer_set_volume(Mixer *mixer, unsigned volume, GError **error_r)
+{
+ WinmmMixer *wm = (WinmmMixer *) mixer;
+ DWORD value = winmm_volume_encode(volume);
+ HWAVEOUT handle = winmm_output_get_handle(wm->output);
+ MMRESULT result = waveOutSetVolume(handle, value);
+
+ if (result != MMSYSERR_NOERROR) {
+ g_set_error(error_r, 0, winmm_mixer_quark(),
+ "Failed to set winmm volume");
+ return false;
+ }
+
+ return true;
+}
+
+const struct mixer_plugin winmm_mixer_plugin = {
+ winmm_mixer_init,
+ winmm_mixer_finish,
+ nullptr,
+ nullptr,
+ winmm_mixer_get_volume,
+ winmm_mixer_set_volume,
+ false,
+};
diff --git a/src/mixer/alsa_mixer_plugin.c b/src/mixer/alsa_mixer_plugin.c
deleted file mode 100644
index 22e4e22bd..000000000
--- a/src/mixer/alsa_mixer_plugin.c
+++ /dev/null
@@ -1,431 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "mixer_api.h"
-#include "output_api.h"
-#include "event_pipe.h"
-
-#include <glib.h>
-#include <alsa/asoundlib.h>
-
-#define VOLUME_MIXER_ALSA_DEFAULT "default"
-#define VOLUME_MIXER_ALSA_CONTROL_DEFAULT "PCM"
-#define VOLUME_MIXER_ALSA_INDEX_DEFAULT 0
-
-struct alsa_mixer_source {
- GSource source;
-
- snd_mixer_t *mixer;
-
- /** a linked list of all registered GPollFD objects */
- GSList *fds;
-};
-
-struct alsa_mixer {
- /** the base mixer class */
- struct mixer base;
-
- const char *device;
- const char *control;
- unsigned int index;
-
- snd_mixer_t *handle;
- snd_mixer_elem_t *elem;
- long volume_min;
- long volume_max;
- int volume_set;
-
- struct alsa_mixer_source *source;
-};
-
-/**
- * The quark used for GError.domain.
- */
-static inline GQuark
-alsa_mixer_quark(void)
-{
- return g_quark_from_static_string("alsa_mixer");
-}
-
-/*
- * GSource helper functions
- *
- */
-
-static GSList **
-find_fd(GSList **list_r, int fd)
-{
- while (true) {
- GSList *list = *list_r;
- if (list == NULL)
- return NULL;
-
- GPollFD *p = list->data;
- if (p->fd == fd)
- return list_r;
-
- list_r = &list->next;
- }
-}
-
-static void
-alsa_mixer_update_fd(struct alsa_mixer_source *source, const struct pollfd *p,
- GSList **old_r)
-{
- GSList **found_r = find_fd(old_r, p->fd);
- if (found_r == NULL) {
- /* new fd */
- GPollFD *q = g_new(GPollFD, 1);
- q->fd = p->fd;
- q->events = p->events;
- g_source_add_poll(&source->source, q);
- source->fds = g_slist_prepend(source->fds, q);
- return;
- }
-
- GSList *found = *found_r;
- *found_r = found->next;
-
- GPollFD *q = found->data;
- if (q->events != p->events) {
- /* refresh events */
- g_source_remove_poll(&source->source, q);
- q->events = p->events;
- g_source_add_poll(&source->source, q);
- }
-
- found->next = source->fds;
- source->fds = found;
-}
-
-static void
-alsa_mixer_update_fds(struct alsa_mixer_source *source)
-{
- int count = snd_mixer_poll_descriptors_count(source->mixer);
- if (count < 0)
- count = 0;
-
- struct pollfd *pfds = g_new(struct pollfd, count);
- count = snd_mixer_poll_descriptors(source->mixer, pfds, count);
- if (count < 0)
- count = 0;
-
- GSList *old = source->fds;
- source->fds = NULL;
-
- for (int i = 0; i < count; ++i)
- alsa_mixer_update_fd(source, &pfds[i], &old);
- g_free(pfds);
-
- for (; old != NULL; old = old->next) {
- GPollFD *q = old->data;
- g_source_remove_poll(&source->source, q);
- g_free(q);
- }
-
- g_slist_free(old);
-}
-
-/*
- * GSource methods
- *
- */
-
-static gboolean
-alsa_mixer_source_prepare(GSource *_source, G_GNUC_UNUSED gint *timeout_r)
-{
- struct alsa_mixer_source *source = (struct alsa_mixer_source *)_source;
- alsa_mixer_update_fds(source);
-
- return false;
-}
-
-static gboolean
-alsa_mixer_source_check(GSource *_source)
-{
- struct alsa_mixer_source *source = (struct alsa_mixer_source *)_source;
-
- for (const GSList *i = source->fds; i != NULL; i = i->next) {
- const GPollFD *poll_fd = i->data;
- if (poll_fd->revents != 0)
- return true;
- }
-
- return false;
-}
-
-static gboolean
-alsa_mixer_source_dispatch(GSource *_source,
- G_GNUC_UNUSED GSourceFunc callback,
- G_GNUC_UNUSED gpointer user_data)
-{
- struct alsa_mixer_source *source = (struct alsa_mixer_source *)_source;
-
- snd_mixer_handle_events(source->mixer);
- return true;
-}
-
-static void
-alsa_mixer_source_finalize(GSource *_source)
-{
- struct alsa_mixer_source *source = (struct alsa_mixer_source *)_source;
-
- for (GSList *i = source->fds; i != NULL; i = i->next)
- g_free(i->data);
-
- g_slist_free(source->fds);
-}
-
-static GSourceFuncs alsa_mixer_source_funcs = {
- .prepare = alsa_mixer_source_prepare,
- .check = alsa_mixer_source_check,
- .dispatch = alsa_mixer_source_dispatch,
- .finalize = alsa_mixer_source_finalize,
-};
-
-/*
- * libasound callbacks
- *
- */
-
-static int
-alsa_mixer_elem_callback(G_GNUC_UNUSED snd_mixer_elem_t *elem, unsigned mask)
-{
- if (mask & SND_CTL_EVENT_MASK_VALUE)
- event_pipe_emit(PIPE_EVENT_MIXER);
-
- return 0;
-}
-
-/*
- * mixer_plugin methods
- *
- */
-
-static struct mixer *
-alsa_mixer_init(G_GNUC_UNUSED void *ao, const struct config_param *param,
- G_GNUC_UNUSED GError **error_r)
-{
- struct alsa_mixer *am = g_new(struct alsa_mixer, 1);
-
- mixer_init(&am->base, &alsa_mixer_plugin);
-
- am->device = config_get_block_string(param, "mixer_device",
- VOLUME_MIXER_ALSA_DEFAULT);
- am->control = config_get_block_string(param, "mixer_control",
- VOLUME_MIXER_ALSA_CONTROL_DEFAULT);
- am->index = config_get_block_unsigned(param, "mixer_index",
- VOLUME_MIXER_ALSA_INDEX_DEFAULT);
-
- return &am->base;
-}
-
-static void
-alsa_mixer_finish(struct mixer *data)
-{
- struct alsa_mixer *am = (struct alsa_mixer *)data;
-
- g_free(am);
-
- /* free libasound's config cache */
- snd_config_update_free_global();
-}
-
-G_GNUC_PURE
-static snd_mixer_elem_t *
-alsa_mixer_lookup_elem(snd_mixer_t *handle, const char *name, unsigned idx)
-{
- for (snd_mixer_elem_t *elem = snd_mixer_first_elem(handle);
- elem != NULL; elem = snd_mixer_elem_next(elem)) {
- if (snd_mixer_elem_get_type(elem) == SND_MIXER_ELEM_SIMPLE &&
- g_ascii_strcasecmp(snd_mixer_selem_get_name(elem),
- name) == 0 &&
- snd_mixer_selem_get_index(elem) == idx)
- return elem;
- }
-
- return NULL;
-}
-
-static bool
-alsa_mixer_setup(struct alsa_mixer *am, GError **error_r)
-{
- int err;
-
- if ((err = snd_mixer_attach(am->handle, am->device)) < 0) {
- g_set_error(error_r, alsa_mixer_quark(), err,
- "failed to attach to %s: %s",
- am->device, snd_strerror(err));
- return false;
- }
-
- if ((err = snd_mixer_selem_register(am->handle, NULL,
- NULL)) < 0) {
- g_set_error(error_r, alsa_mixer_quark(), err,
- "snd_mixer_selem_register() failed: %s",
- snd_strerror(err));
- return false;
- }
-
- if ((err = snd_mixer_load(am->handle)) < 0) {
- g_set_error(error_r, alsa_mixer_quark(), err,
- "snd_mixer_load() failed: %s\n",
- snd_strerror(err));
- return false;
- }
-
- am->elem = alsa_mixer_lookup_elem(am->handle, am->control, am->index);
- if (am->elem == NULL) {
- g_set_error(error_r, alsa_mixer_quark(), 0,
- "no such mixer control: %s", am->control);
- return false;
- }
-
- snd_mixer_selem_get_playback_volume_range(am->elem,
- &am->volume_min,
- &am->volume_max);
-
- snd_mixer_elem_set_callback(am->elem, alsa_mixer_elem_callback);
-
- am->source = (struct alsa_mixer_source *)
- g_source_new(&alsa_mixer_source_funcs, sizeof(*am->source));
- am->source->mixer = am->handle;
- am->source->fds = NULL;
- g_source_attach(&am->source->source, g_main_context_default());
-
- return true;
-}
-
-static bool
-alsa_mixer_open(struct mixer *data, GError **error_r)
-{
- struct alsa_mixer *am = (struct alsa_mixer *)data;
- int err;
-
- am->volume_set = -1;
-
- err = snd_mixer_open(&am->handle, 0);
- if (err < 0) {
- g_set_error(error_r, alsa_mixer_quark(), err,
- "snd_mixer_open() failed: %s", snd_strerror(err));
- return false;
- }
-
- if (!alsa_mixer_setup(am, error_r)) {
- snd_mixer_close(am->handle);
- return false;
- }
-
- return true;
-}
-
-static void
-alsa_mixer_close(struct mixer *data)
-{
- struct alsa_mixer *am = (struct alsa_mixer *)data;
-
- assert(am->handle != NULL);
-
- g_source_destroy(&am->source->source);
- g_source_unref(&am->source->source);
-
- snd_mixer_elem_set_callback(am->elem, NULL);
- snd_mixer_close(am->handle);
-}
-
-static int
-alsa_mixer_get_volume(struct mixer *mixer, GError **error_r)
-{
- struct alsa_mixer *am = (struct alsa_mixer *)mixer;
- int err;
- int ret;
- long level;
-
- assert(am->handle != NULL);
-
- err = snd_mixer_handle_events(am->handle);
- if (err < 0) {
- g_set_error(error_r, alsa_mixer_quark(), err,
- "snd_mixer_handle_events() failed: %s",
- snd_strerror(err));
- return false;
- }
-
- err = snd_mixer_selem_get_playback_volume(am->elem,
- SND_MIXER_SCHN_FRONT_LEFT,
- &level);
- if (err < 0) {
- g_set_error(error_r, alsa_mixer_quark(), err,
- "failed to read ALSA volume: %s",
- snd_strerror(err));
- return false;
- }
-
- ret = ((am->volume_set / 100.0) * (am->volume_max - am->volume_min)
- + am->volume_min) + 0.5;
- if (am->volume_set > 0 && ret == level) {
- ret = am->volume_set;
- } else {
- ret = (int)(100 * (((float)(level - am->volume_min)) /
- (am->volume_max - am->volume_min)) + 0.5);
- }
-
- return ret;
-}
-
-static bool
-alsa_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r)
-{
- struct alsa_mixer *am = (struct alsa_mixer *)mixer;
- float vol;
- long level;
- int err;
-
- assert(am->handle != NULL);
-
- vol = volume;
-
- am->volume_set = vol + 0.5;
-
- level = (long)(((vol / 100.0) * (am->volume_max - am->volume_min) +
- am->volume_min) + 0.5);
- level = level > am->volume_max ? am->volume_max : level;
- level = level < am->volume_min ? am->volume_min : level;
-
- err = snd_mixer_selem_set_playback_volume_all(am->elem, level);
- if (err < 0) {
- g_set_error(error_r, alsa_mixer_quark(), err,
- "failed to set ALSA volume: %s",
- snd_strerror(err));
- return false;
- }
-
- return true;
-}
-
-const struct mixer_plugin alsa_mixer_plugin = {
- .init = alsa_mixer_init,
- .finish = alsa_mixer_finish,
- .open = alsa_mixer_open,
- .close = alsa_mixer_close,
- .get_volume = alsa_mixer_get_volume,
- .set_volume = alsa_mixer_set_volume,
- .global = true,
-};
diff --git a/src/mixer/oss_mixer_plugin.c b/src/mixer/oss_mixer_plugin.c
deleted file mode 100644
index 608f1f9b8..000000000
--- a/src/mixer/oss_mixer_plugin.c
+++ /dev/null
@@ -1,216 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "mixer_api.h"
-#include "output_api.h"
-#include "fd_util.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <sys/stat.h>
-#include <sys/ioctl.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <stdlib.h>
-#include <unistd.h>
-
-#if defined(__OpenBSD__) || defined(__NetBSD__)
-# include <soundcard.h>
-#else /* !(defined(__OpenBSD__) || defined(__NetBSD__) */
-# include <sys/soundcard.h>
-#endif /* !(defined(__OpenBSD__) || defined(__NetBSD__) */
-
-#define VOLUME_MIXER_OSS_DEFAULT "/dev/mixer"
-
-struct oss_mixer {
- /** the base mixer class */
- struct mixer base;
-
- const char *device;
- const char *control;
-
- int device_fd;
- int volume_control;
-};
-
-/**
- * The quark used for GError.domain.
- */
-static inline GQuark
-oss_mixer_quark(void)
-{
- return g_quark_from_static_string("oss_mixer");
-}
-
-static int
-oss_find_mixer(const char *name)
-{
- const char *labels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS;
- size_t name_length = strlen(name);
-
- for (unsigned i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
- if (g_ascii_strncasecmp(name, labels[i], name_length) == 0 &&
- (labels[i][name_length] == 0 ||
- labels[i][name_length] == ' '))
- return i;
- }
- return -1;
-}
-
-static struct mixer *
-oss_mixer_init(G_GNUC_UNUSED void *ao, const struct config_param *param,
- GError **error_r)
-{
- struct oss_mixer *om = g_new(struct oss_mixer, 1);
-
- mixer_init(&om->base, &oss_mixer_plugin);
-
- om->device = config_get_block_string(param, "mixer_device",
- VOLUME_MIXER_OSS_DEFAULT);
- om->control = config_get_block_string(param, "mixer_control", NULL);
-
- if (om->control != NULL) {
- om->volume_control = oss_find_mixer(om->control);
- if (om->volume_control < 0) {
- g_free(om);
- g_set_error(error_r, oss_mixer_quark(), 0,
- "no such mixer control: %s", om->control);
- return NULL;
- }
- } else
- om->volume_control = SOUND_MIXER_PCM;
-
- return &om->base;
-}
-
-static void
-oss_mixer_finish(struct mixer *data)
-{
- struct oss_mixer *om = (struct oss_mixer *) data;
-
- g_free(om);
-}
-
-static void
-oss_mixer_close(struct mixer *data)
-{
- struct oss_mixer *om = (struct oss_mixer *) data;
-
- assert(om->device_fd >= 0);
-
- close(om->device_fd);
-}
-
-static bool
-oss_mixer_open(struct mixer *data, GError **error_r)
-{
- struct oss_mixer *om = (struct oss_mixer *) data;
-
- om->device_fd = open_cloexec(om->device, O_RDONLY, 0);
- if (om->device_fd < 0) {
- g_set_error(error_r, oss_mixer_quark(), errno,
- "failed to open %s: %s",
- om->device, g_strerror(errno));
- return false;
- }
-
- if (om->control) {
- int devmask = 0;
-
- if (ioctl(om->device_fd, SOUND_MIXER_READ_DEVMASK, &devmask) < 0) {
- g_set_error(error_r, oss_mixer_quark(), errno,
- "READ_DEVMASK failed: %s",
- g_strerror(errno));
- oss_mixer_close(data);
- return false;
- }
-
- if (((1 << om->volume_control) & devmask) == 0) {
- g_set_error(error_r, oss_mixer_quark(), 0,
- "mixer control \"%s\" not usable",
- om->control);
- oss_mixer_close(data);
- return false;
- }
- }
- return true;
-}
-
-static int
-oss_mixer_get_volume(struct mixer *mixer, GError **error_r)
-{
- struct oss_mixer *om = (struct oss_mixer *)mixer;
- int left, right, level;
- int ret;
-
- assert(om->device_fd >= 0);
-
- ret = ioctl(om->device_fd, MIXER_READ(om->volume_control), &level);
- if (ret < 0) {
- g_set_error(error_r, oss_mixer_quark(), errno,
- "failed to read OSS volume: %s",
- g_strerror(errno));
- return false;
- }
-
- left = level & 0xff;
- right = (level & 0xff00) >> 8;
-
- if (left != right) {
- g_warning("volume for left and right is not the same, \"%i\" and "
- "\"%i\"\n", left, right);
- }
-
- return left;
-}
-
-static bool
-oss_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r)
-{
- struct oss_mixer *om = (struct oss_mixer *)mixer;
- int level;
- int ret;
-
- assert(om->device_fd >= 0);
- assert(volume <= 100);
-
- level = (volume << 8) + volume;
-
- ret = ioctl(om->device_fd, MIXER_WRITE(om->volume_control), &level);
- if (ret < 0) {
- g_set_error(error_r, oss_mixer_quark(), errno,
- "failed to set OSS volume: %s",
- g_strerror(errno));
- return false;
- }
-
- return true;
-}
-
-const struct mixer_plugin oss_mixer_plugin = {
- .init = oss_mixer_init,
- .finish = oss_mixer_finish,
- .open = oss_mixer_open,
- .close = oss_mixer_close,
- .get_volume = oss_mixer_get_volume,
- .set_volume = oss_mixer_set_volume,
- .global = true,
-};
diff --git a/src/mixer/pulse_mixer_plugin.c b/src/mixer/pulse_mixer_plugin.c
deleted file mode 100644
index a82c032b3..000000000
--- a/src/mixer/pulse_mixer_plugin.c
+++ /dev/null
@@ -1,236 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "pulse_mixer_plugin.h"
-#include "mixer_api.h"
-#include "output/pulse_output_plugin.h"
-#include "conf.h"
-#include "event_pipe.h"
-
-#include <glib.h>
-
-#include <pulse/thread-mainloop.h>
-#include <pulse/context.h>
-#include <pulse/introspect.h>
-#include <pulse/stream.h>
-#include <pulse/subscribe.h>
-#include <pulse/error.h>
-
-#include <assert.h>
-#include <string.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "pulse_mixer"
-
-struct pulse_mixer {
- struct mixer base;
-
- struct pulse_output *output;
-
- bool online;
- struct pa_cvolume volume;
-
-};
-
-/**
- * The quark used for GError.domain.
- */
-static inline GQuark
-pulse_mixer_quark(void)
-{
- return g_quark_from_static_string("pulse_mixer");
-}
-
-static void
-pulse_mixer_offline(struct pulse_mixer *pm)
-{
- if (!pm->online)
- return;
-
- pm->online = false;
-
- event_pipe_emit(PIPE_EVENT_MIXER);
-}
-
-/**
- * Callback invoked by pulse_mixer_update(). Receives the new mixer
- * value.
- */
-static void
-pulse_mixer_volume_cb(G_GNUC_UNUSED pa_context *context, const pa_sink_input_info *i,
- int eol, void *userdata)
-{
- struct pulse_mixer *pm = userdata;
-
- if (eol)
- return;
-
- if (i == NULL) {
- pulse_mixer_offline(pm);
- return;
- }
-
- pm->online = true;
- pm->volume = i->volume;
-
- event_pipe_emit(PIPE_EVENT_MIXER);
-}
-
-static void
-pulse_mixer_update(struct pulse_mixer *pm,
- struct pa_context *context, struct pa_stream *stream)
-{
- pa_operation *o;
-
- assert(context != NULL);
- assert(stream != NULL);
- assert(pa_stream_get_state(stream) == PA_STREAM_READY);
-
- o = pa_context_get_sink_input_info(context,
- pa_stream_get_index(stream),
- pulse_mixer_volume_cb, pm);
- if (o == NULL) {
- g_warning("pa_context_get_sink_input_info() failed: %s",
- pa_strerror(pa_context_errno(context)));
- pulse_mixer_offline(pm);
- return;
- }
-
- pa_operation_unref(o);
-}
-
-void
-pulse_mixer_on_connect(G_GNUC_UNUSED struct pulse_mixer *pm,
- struct pa_context *context)
-{
- pa_operation *o;
-
- assert(context != NULL);
-
- o = pa_context_subscribe(context,
- (pa_subscription_mask_t)PA_SUBSCRIPTION_MASK_SINK_INPUT,
- NULL, NULL);
- if (o == NULL) {
- g_warning("pa_context_subscribe() failed: %s",
- pa_strerror(pa_context_errno(context)));
- return;
- }
-
- pa_operation_unref(o);
-}
-
-void
-pulse_mixer_on_disconnect(struct pulse_mixer *pm)
-{
- pulse_mixer_offline(pm);
-}
-
-void
-pulse_mixer_on_change(struct pulse_mixer *pm,
- struct pa_context *context, struct pa_stream *stream)
-{
- pulse_mixer_update(pm, context, stream);
-}
-
-static struct mixer *
-pulse_mixer_init(void *ao, G_GNUC_UNUSED const struct config_param *param,
- GError **error_r)
-{
- struct pulse_mixer *pm;
- struct pulse_output *po = ao;
-
- if (ao == NULL) {
- g_set_error(error_r, pulse_mixer_quark(), 0,
- "The pulse mixer cannot work without the audio output");
- return false;
- }
-
- pm = g_new(struct pulse_mixer,1);
- mixer_init(&pm->base, &pulse_mixer_plugin);
-
- pm->online = false;
- pm->output = po;
-
- pulse_output_set_mixer(po, pm);
-
- return &pm->base;
-}
-
-static void
-pulse_mixer_finish(struct mixer *data)
-{
- struct pulse_mixer *pm = (struct pulse_mixer *) data;
-
- pulse_output_clear_mixer(pm->output, pm);
-
- /* free resources */
-
- g_free(pm);
-}
-
-static int
-pulse_mixer_get_volume(struct mixer *mixer, G_GNUC_UNUSED GError **error_r)
-{
- struct pulse_mixer *pm = (struct pulse_mixer *) mixer;
- int ret;
-
- pulse_output_lock(pm->output);
-
- ret = pm->online
- ? (int)((100*(pa_cvolume_avg(&pm->volume)+1))/PA_VOLUME_NORM)
- : -1;
-
- pulse_output_unlock(pm->output);
-
- return ret;
-}
-
-static bool
-pulse_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r)
-{
- struct pulse_mixer *pm = (struct pulse_mixer *) mixer;
- struct pa_cvolume cvolume;
- bool success;
-
- pulse_output_lock(pm->output);
-
- if (!pm->online) {
- pulse_output_unlock(pm->output);
- g_set_error(error_r, pulse_mixer_quark(), 0, "disconnected");
- return false;
- }
-
- pa_cvolume_set(&cvolume, pm->volume.channels,
- (pa_volume_t)volume * PA_VOLUME_NORM / 100 + 0.5);
- success = pulse_output_set_volume(pm->output, &cvolume, error_r);
- if (success)
- pm->volume = cvolume;
-
- pulse_output_unlock(pm->output);
-
- return success;
-}
-
-const struct mixer_plugin pulse_mixer_plugin = {
- .init = pulse_mixer_init,
- .finish = pulse_mixer_finish,
- .get_volume = pulse_mixer_get_volume,
- .set_volume = pulse_mixer_set_volume,
-};
diff --git a/src/mixer/pulse_mixer_plugin.h b/src/mixer/pulse_mixer_plugin.h
deleted file mode 100644
index 461633d37..000000000
--- a/src/mixer/pulse_mixer_plugin.h
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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_PULSE_MIXER_PLUGIN_H
-#define MPD_PULSE_MIXER_PLUGIN_H
-
-#include <pulse/def.h>
-
-struct pulse_mixer;
-struct pa_context;
-struct pa_stream;
-
-void
-pulse_mixer_on_connect(struct pulse_mixer *pm, struct pa_context *context);
-
-void
-pulse_mixer_on_disconnect(struct pulse_mixer *pm);
-
-void
-pulse_mixer_on_change(struct pulse_mixer *pm,
- struct pa_context *context, struct pa_stream *stream);
-
-#endif
diff --git a/src/mixer/roar_mixer_plugin.c b/src/mixer/roar_mixer_plugin.c
deleted file mode 100644
index 47d3c17f9..000000000
--- a/src/mixer/roar_mixer_plugin.c
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
- * Copyright (C) 2010-2011 Philipp 'ph3-der-loewe' Schafft
- * Copyright (C) 2010-2011 Hans-Kristian 'maister' Arntzen
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-
-#include "config.h"
-#include "mixer_api.h"
-#include "output_api.h"
-#include "output/roar_output_plugin.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <stdlib.h>
-#include <unistd.h>
-
-typedef struct roar_mpd_mixer
-{
- /** the base mixer class */
- struct mixer base;
- struct roar *self;
-} roar_mixer_t;
-
-/**
- * The quark used for GError.domain.
- */
-static inline GQuark
-roar_mixer_quark(void)
-{
- return g_quark_from_static_string("roar_mixer");
-}
-
-static struct mixer *
-roar_mixer_init(void *ao, G_GNUC_UNUSED const struct config_param *param,
- G_GNUC_UNUSED GError **error_r)
-{
- roar_mixer_t *self = g_new(roar_mixer_t, 1);
- self->self = ao;
-
- mixer_init(&self->base, &roar_mixer_plugin);
-
- return &self->base;
-}
-
-static void
-roar_mixer_finish(struct mixer *data)
-{
- roar_mixer_t *self = (roar_mixer_t *) data;
-
- g_free(self);
-}
-
-static void
-roar_mixer_close(G_GNUC_UNUSED struct mixer *data)
-{
-}
-
-static bool
-roar_mixer_open(G_GNUC_UNUSED struct mixer *data,
- G_GNUC_UNUSED GError **error_r)
-{
- return true;
-}
-
-static int
-roar_mixer_get_volume(struct mixer *mixer, G_GNUC_UNUSED GError **error_r)
-{
- roar_mixer_t *self = (roar_mixer_t *)mixer;
- return roar_output_get_volume(self->self);
-}
-
-static bool
-roar_mixer_set_volume(struct mixer *mixer, unsigned volume,
- G_GNUC_UNUSED GError **error_r)
-{
- roar_mixer_t *self = (roar_mixer_t *)mixer;
- return roar_output_set_volume(self->self, volume);
-}
-
-const struct mixer_plugin roar_mixer_plugin = {
- .init = roar_mixer_init,
- .finish = roar_mixer_finish,
- .open = roar_mixer_open,
- .close = roar_mixer_close,
- .get_volume = roar_mixer_get_volume,
- .set_volume = roar_mixer_set_volume,
- .global = false,
-};
diff --git a/src/mixer/software_mixer_plugin.c b/src/mixer/software_mixer_plugin.c
deleted file mode 100644
index 0206c3b99..000000000
--- a/src/mixer/software_mixer_plugin.c
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "software_mixer_plugin.h"
-#include "mixer_api.h"
-#include "filter_plugin.h"
-#include "filter_registry.h"
-#include "filter/volume_filter_plugin.h"
-#include "pcm_volume.h"
-
-#include <assert.h>
-#include <math.h>
-
-struct software_mixer {
- /** the base mixer class */
- struct mixer base;
-
- struct filter *filter;
-
- unsigned volume;
-};
-
-static struct mixer *
-software_mixer_init(G_GNUC_UNUSED void *ao,
- G_GNUC_UNUSED const struct config_param *param,
- G_GNUC_UNUSED GError **error_r)
-{
- struct software_mixer *sm = g_new(struct software_mixer, 1);
-
- mixer_init(&sm->base, &software_mixer_plugin);
-
- sm->filter = filter_new(&volume_filter_plugin, NULL, NULL);
- assert(sm->filter != NULL);
-
- sm->volume = 100;
-
- return &sm->base;
-}
-
-static void
-software_mixer_finish(struct mixer *data)
-{
- struct software_mixer *sm = (struct software_mixer *)data;
-
- g_free(sm);
-}
-
-static int
-software_mixer_get_volume(struct mixer *mixer, G_GNUC_UNUSED GError **error_r)
-{
- struct software_mixer *sm = (struct software_mixer *)mixer;
-
- return sm->volume;
-}
-
-static bool
-software_mixer_set_volume(struct mixer *mixer, unsigned volume,
- G_GNUC_UNUSED GError **error_r)
-{
- struct software_mixer *sm = (struct software_mixer *)mixer;
-
- assert(volume <= 100);
-
- sm->volume = volume;
-
- if (volume >= 100)
- volume = PCM_VOLUME_1;
- else if (volume > 0)
- volume = pcm_float_to_volume((exp(volume / 25.0) - 1) /
- (54.5981500331F - 1));
-
- volume_filter_set(sm->filter, volume);
- return true;
-}
-
-const struct mixer_plugin software_mixer_plugin = {
- .init = software_mixer_init,
- .finish = software_mixer_finish,
- .get_volume = software_mixer_get_volume,
- .set_volume = software_mixer_set_volume,
- .global = true,
-};
-
-struct filter *
-software_mixer_get_filter(struct mixer *mixer)
-{
- struct software_mixer *sm = (struct software_mixer *)mixer;
-
- assert(sm->base.plugin == &software_mixer_plugin);
-
- return sm->filter;
-}
diff --git a/src/mixer/software_mixer_plugin.h b/src/mixer/software_mixer_plugin.h
deleted file mode 100644
index ee2b2023c..000000000
--- a/src/mixer/software_mixer_plugin.h
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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 SOFTWARE_MIXER_PLUGIN_H
-#define SOFTWARE_MIXER_PLUGIN_H
-
-struct mixer;
-struct filter;
-
-/**
- * Returns the (volume) filter associated with this mixer. All users
- * of this mixer plugin should install this filter.
- */
-struct filter *
-software_mixer_get_filter(struct mixer *mixer);
-
-#endif
diff --git a/src/mixer/winmm_mixer_plugin.c b/src/mixer/winmm_mixer_plugin.c
deleted file mode 100644
index ceddf6afd..000000000
--- a/src/mixer/winmm_mixer_plugin.c
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "mixer_api.h"
-#include "output_api.h"
-#include "output/winmm_output_plugin.h"
-
-#include <assert.h>
-#include <math.h>
-#include <windows.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "winmm_mixer"
-
-struct winmm_mixer {
- struct mixer base;
- struct winmm_output *output;
-};
-
-static inline GQuark
-winmm_mixer_quark(void)
-{
- return g_quark_from_static_string("winmm_mixer");
-}
-
-static inline int
-winmm_volume_decode(DWORD volume)
-{
- return lround((volume & 0xFFFF) / 655.35);
-}
-
-static inline DWORD
-winmm_volume_encode(int volume)
-{
- int value = lround(volume * 655.35);
- return MAKELONG(value, value);
-}
-
-static struct mixer *
-winmm_mixer_init(void *ao, G_GNUC_UNUSED const struct config_param *param,
- G_GNUC_UNUSED GError **error_r)
-{
- assert(ao != NULL);
-
- struct winmm_mixer *wm = g_new(struct winmm_mixer, 1);
- mixer_init(&wm->base, &winmm_mixer_plugin);
- wm->output = (struct winmm_output *) ao;
-
- return &wm->base;
-}
-
-static void
-winmm_mixer_finish(struct mixer *data)
-{
- g_free(data);
-}
-
-static int
-winmm_mixer_get_volume(struct mixer *mixer, GError **error_r)
-{
- struct winmm_mixer *wm = (struct winmm_mixer *) mixer;
- DWORD volume;
- HWAVEOUT handle = winmm_output_get_handle(wm->output);
- MMRESULT result = waveOutGetVolume(handle, &volume);
-
- if (result != MMSYSERR_NOERROR) {
- g_set_error(error_r, 0, winmm_mixer_quark(),
- "Failed to get winmm volume");
- return -1;
- }
-
- return winmm_volume_decode(volume);
-}
-
-static bool
-winmm_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r)
-{
- struct winmm_mixer *wm = (struct winmm_mixer *) mixer;
- DWORD value = winmm_volume_encode(volume);
- HWAVEOUT handle = winmm_output_get_handle(wm->output);
- MMRESULT result = waveOutSetVolume(handle, value);
-
- if (result != MMSYSERR_NOERROR) {
- g_set_error(error_r, 0, winmm_mixer_quark(),
- "Failed to set winmm volume");
- return false;
- }
-
- return true;
-}
-
-const struct mixer_plugin winmm_mixer_plugin = {
- .init = winmm_mixer_init,
- .finish = winmm_mixer_finish,
- .get_volume = winmm_mixer_get_volume,
- .set_volume = winmm_mixer_set_volume,
-};
diff --git a/src/mixer_all.c b/src/mixer_all.c
deleted file mode 100644
index 95ba90793..000000000
--- a/src/mixer_all.c
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "mixer_all.h"
-#include "mixer_control.h"
-#include "output_all.h"
-#include "output_plugin.h"
-#include "output_internal.h"
-#include "pcm_volume.h"
-#include "mixer_api.h"
-#include "mixer_list.h"
-
-#include <glib.h>
-
-#include <assert.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "mixer"
-
-static int
-output_mixer_get_volume(unsigned i)
-{
- struct audio_output *output;
- struct mixer *mixer;
- int volume;
- GError *error = NULL;
-
- assert(i < audio_output_count());
-
- output = audio_output_get(i);
- if (!output->enabled)
- return -1;
-
- mixer = output->mixer;
- if (mixer == NULL)
- return -1;
-
- volume = mixer_get_volume(mixer, &error);
- if (volume < 0 && error != NULL) {
- g_warning("Failed to read mixer for '%s': %s",
- output->name, error->message);
- g_error_free(error);
- }
-
- return volume;
-}
-
-int
-mixer_all_get_volume(void)
-{
- unsigned count = audio_output_count(), ok = 0;
- int volume, total = 0;
-
- for (unsigned i = 0; i < count; i++) {
- volume = output_mixer_get_volume(i);
- if (volume >= 0) {
- total += volume;
- ++ok;
- }
- }
-
- if (ok == 0)
- return -1;
-
- return total / ok;
-}
-
-static bool
-output_mixer_set_volume(unsigned i, unsigned volume)
-{
- struct audio_output *output;
- struct mixer *mixer;
- bool success;
- GError *error = NULL;
-
- assert(i < audio_output_count());
- assert(volume <= 100);
-
- output = audio_output_get(i);
- if (!output->enabled)
- return false;
-
- mixer = output->mixer;
- if (mixer == NULL)
- return false;
-
- success = mixer_set_volume(mixer, volume, &error);
- if (!success && error != NULL) {
- g_warning("Failed to set mixer for '%s': %s",
- output->name, error->message);
- g_error_free(error);
- }
-
- return success;
-}
-
-bool
-mixer_all_set_volume(unsigned volume)
-{
- bool success = false;
- unsigned count = audio_output_count();
-
- assert(volume <= 100);
-
- for (unsigned i = 0; i < count; i++)
- success = output_mixer_set_volume(i, volume)
- || success;
-
- return success;
-}
-
-static int
-output_mixer_get_software_volume(unsigned i)
-{
- struct audio_output *output;
- struct mixer *mixer;
-
- assert(i < audio_output_count());
-
- output = audio_output_get(i);
- if (!output->enabled)
- return -1;
-
- mixer = output->mixer;
- if (mixer == NULL || mixer->plugin != &software_mixer_plugin)
- return -1;
-
- return mixer_get_volume(mixer, NULL);
-}
-
-int
-mixer_all_get_software_volume(void)
-{
- unsigned count = audio_output_count(), ok = 0;
- int volume, total = 0;
-
- for (unsigned i = 0; i < count; i++) {
- volume = output_mixer_get_software_volume(i);
- if (volume >= 0) {
- total += volume;
- ++ok;
- }
- }
-
- if (ok == 0)
- return -1;
-
- return total / ok;
-}
-
-void
-mixer_all_set_software_volume(unsigned volume)
-{
- unsigned count = audio_output_count();
-
- assert(volume <= PCM_VOLUME_1);
-
- for (unsigned i = 0; i < count; i++) {
- struct audio_output *output = audio_output_get(i);
- if (output->mixer != NULL &&
- output->mixer->plugin == &software_mixer_plugin)
- mixer_set_volume(output->mixer, volume, NULL);
- }
-}
diff --git a/src/mixer_all.h b/src/mixer_all.h
deleted file mode 100644
index fe873e713..000000000
--- a/src/mixer_all.h
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * 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.
- */
-
-/** \file
- *
- * Functions which affect the mixers of all audio outputs.
- */
-
-#ifndef MPD_MIXER_ALL_H
-#define MPD_MIXER_ALL_H
-
-#include <stdbool.h>
-
-/**
- * Returns the average volume of all available mixers (range 0..100).
- * Returns -1 if no mixer can be queried.
- */
-int
-mixer_all_get_volume(void);
-
-/**
- * Sets the volume on all available mixers.
- *
- * @param volume the volume (range 0..100)
- * @return true on success, false on failure
- */
-bool
-mixer_all_set_volume(unsigned volume);
-
-/**
- * Similar to mixer_all_get_volume(), but gets the volume only for
- * software mixers. See #software_mixer_plugin. This function fails
- * if no software mixer is configured.
- */
-int
-mixer_all_get_software_volume(void);
-
-/**
- * Similar to mixer_all_set_volume(), but sets the volume only for
- * software mixers. See #software_mixer_plugin. This function cannot
- * fail, because the underlying software mixers cannot fail either.
- */
-void
-mixer_all_set_software_volume(unsigned volume);
-
-#endif
diff --git a/src/mixer_api.c b/src/mixer_api.c
deleted file mode 100644
index c85916c94..000000000
--- a/src/mixer_api.c
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "mixer_api.h"
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "mixer"
-
-void
-mixer_init(struct mixer *mixer, const struct mixer_plugin *plugin)
-{
- mixer->plugin = plugin;
- mixer->mutex = g_mutex_new();
- mixer->open = false;
- mixer->failed = false;
-}
diff --git a/src/mixer_api.h b/src/mixer_api.h
deleted file mode 100644
index 29c1e00ca..000000000
--- a/src/mixer_api.h
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * 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_MIXER_H
-#define MPD_MIXER_H
-
-#include "mixer_plugin.h"
-#include "mixer_list.h"
-
-#include <glib.h>
-
-struct mixer {
- const struct mixer_plugin *plugin;
-
- /**
- * This mutex protects all of the mixer struct, including its
- * implementation, so plugins don't have to deal with that.
- */
- GMutex *mutex;
-
- /**
- * Is the mixer device currently open?
- */
- bool open;
-
- /**
- * Has this mixer failed, and should not be reopened
- * automatically?
- */
- bool failed;
-};
-
-void
-mixer_init(struct mixer *mixer, const struct mixer_plugin *plugin);
-
-#endif
diff --git a/src/mixer_control.c b/src/mixer_control.c
deleted file mode 100644
index 3e984dd04..000000000
--- a/src/mixer_control.c
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "mixer_control.h"
-#include "mixer_api.h"
-
-#include <assert.h>
-#include <stddef.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "mixer"
-
-struct mixer *
-mixer_new(const struct mixer_plugin *plugin, void *ao,
- const struct config_param *param,
- GError **error_r)
-{
- struct mixer *mixer;
-
- assert(plugin != NULL);
-
- mixer = plugin->init(ao, param, error_r);
-
- assert(mixer == NULL || mixer->plugin == plugin);
-
- return mixer;
-}
-
-void
-mixer_free(struct mixer *mixer)
-{
- assert(mixer != NULL);
- assert(mixer->plugin != NULL);
- assert(mixer->mutex != NULL);
-
- /* mixers with the "global" flag set might still be open at
- this point (see mixer_auto_close()) */
- mixer_close(mixer);
-
- g_mutex_free(mixer->mutex);
-
- mixer->plugin->finish(mixer);
-}
-
-bool
-mixer_open(struct mixer *mixer, GError **error_r)
-{
- bool success;
-
- assert(mixer != NULL);
- assert(mixer->plugin != NULL);
-
- g_mutex_lock(mixer->mutex);
-
- if (mixer->open)
- success = true;
- else if (mixer->plugin->open == NULL)
- success = mixer->open = true;
- else
- success = mixer->open = mixer->plugin->open(mixer, error_r);
-
- mixer->failed = !success;
-
- g_mutex_unlock(mixer->mutex);
-
- return success;
-}
-
-static void
-mixer_close_internal(struct mixer *mixer)
-{
- assert(mixer != NULL);
- assert(mixer->plugin != NULL);
- assert(mixer->open);
-
- if (mixer->plugin->close != NULL)
- mixer->plugin->close(mixer);
-
- mixer->open = false;
-}
-
-void
-mixer_close(struct mixer *mixer)
-{
- assert(mixer != NULL);
- assert(mixer->plugin != NULL);
-
- g_mutex_lock(mixer->mutex);
-
- if (mixer->open)
- mixer_close_internal(mixer);
-
- g_mutex_unlock(mixer->mutex);
-}
-
-void
-mixer_auto_close(struct mixer *mixer)
-{
- if (!mixer->plugin->global)
- mixer_close(mixer);
-}
-
-/*
- * Close the mixer due to failure. The mutex must be locked before
- * calling this function.
- */
-static void
-mixer_failed(struct mixer *mixer)
-{
- assert(mixer->open);
-
- mixer_close_internal(mixer);
-
- mixer->failed = true;
-}
-
-int
-mixer_get_volume(struct mixer *mixer, GError **error_r)
-{
- int volume;
-
- assert(mixer != NULL);
-
- if (mixer->plugin->global && !mixer->failed &&
- !mixer_open(mixer, error_r))
- return -1;
-
- g_mutex_lock(mixer->mutex);
-
- if (mixer->open) {
- GError *error = NULL;
-
- volume = mixer->plugin->get_volume(mixer, &error);
- if (volume < 0 && error != NULL) {
- g_propagate_error(error_r, error);
- mixer_failed(mixer);
- }
- } else
- volume = -1;
-
- g_mutex_unlock(mixer->mutex);
-
- return volume;
-}
-
-bool
-mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r)
-{
- bool success;
-
- assert(mixer != NULL);
- assert(volume <= 100);
-
- if (mixer->plugin->global && !mixer->failed &&
- !mixer_open(mixer, error_r))
- return false;
-
- g_mutex_lock(mixer->mutex);
-
- if (mixer->open) {
- success = mixer->plugin->set_volume(mixer, volume, error_r);
- } else
- success = false;
-
- g_mutex_unlock(mixer->mutex);
-
- return success;
-}
diff --git a/src/mixer_control.h b/src/mixer_control.h
deleted file mode 100644
index 6c3468aca..000000000
--- a/src/mixer_control.h
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * 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.
- */
-
-/** \file
- *
- * Functions which manipulate a #mixer object.
- */
-
-#ifndef MPD_MIXER_CONTROL_H
-#define MPD_MIXER_CONTROL_H
-
-#include <glib.h>
-
-#include <stdbool.h>
-
-struct mixer;
-struct mixer_plugin;
-struct config_param;
-
-struct mixer *
-mixer_new(const struct mixer_plugin *plugin, void *ao,
- const struct config_param *param,
- GError **error_r);
-
-void
-mixer_free(struct mixer *mixer);
-
-bool
-mixer_open(struct mixer *mixer, GError **error_r);
-
-void
-mixer_close(struct mixer *mixer);
-
-/**
- * Close the mixer unless the plugin's "global" flag is set. This is
- * called when the #audio_output is closed.
- */
-void
-mixer_auto_close(struct mixer *mixer);
-
-int
-mixer_get_volume(struct mixer *mixer, GError **error_r);
-
-bool
-mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r);
-
-#endif
diff --git a/src/mixer_list.h b/src/mixer_list.h
deleted file mode 100644
index 078358ec3..000000000
--- a/src/mixer_list.h
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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.
- */
-
-/** \file
- *
- * This header provides "extern" declarations for all mixer plugins.
- */
-
-#ifndef MPD_MIXER_LIST_H
-#define MPD_MIXER_LIST_H
-
-extern const struct mixer_plugin software_mixer_plugin;
-extern const struct mixer_plugin alsa_mixer_plugin;
-extern const struct mixer_plugin oss_mixer_plugin;
-extern const struct mixer_plugin roar_mixer_plugin;
-extern const struct mixer_plugin pulse_mixer_plugin;
-extern const struct mixer_plugin winmm_mixer_plugin;
-
-#endif
diff --git a/src/mixer_plugin.h b/src/mixer_plugin.h
deleted file mode 100644
index 9532b95cb..000000000
--- a/src/mixer_plugin.h
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * 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.
- */
-
-/** \file
- *
- * This header declares the mixer_plugin class. It should not be
- * included directly; use mixer_api.h instead in mixer
- * implementations.
- */
-
-#ifndef MPD_MIXER_PLUGIN_H
-#define MPD_MIXER_PLUGIN_H
-
-#include <glib.h>
-
-#include <stdbool.h>
-
-struct config_param;
-struct mixer;
-
-struct mixer_plugin {
- /**
- * Alocates and configures a mixer device.
- *
- * @param ao the pointer returned by audio_output_plugin.init
- * @param param the configuration section, or NULL if there is
- * no configuration
- * @param error_r location to store the error occurring, or
- * NULL to ignore errors
- * @return a mixer object, or NULL on error
- */
- struct mixer *(*init)(void *ao, const struct config_param *param,
- GError **error_r);
-
- /**
- * Finish and free mixer data
- */
- void (*finish)(struct mixer *data);
-
- /**
- * Open mixer device
- *
- * @param error_r location to store the error occurring, or
- * NULL to ignore errors
- * @return true on success, false on error
- */
- bool (*open)(struct mixer *data, GError **error_r);
-
- /**
- * Close mixer device
- */
- void (*close)(struct mixer *data);
-
- /**
- * Reads the current volume.
- *
- * @param error_r location to store the error occurring, or
- * NULL to ignore errors
- * @return the current volume (0..100 including) or -1 if
- * unavailable or on error (error_r set, mixer will be closed)
- */
- int (*get_volume)(struct mixer *mixer, GError **error_r);
-
- /**
- * Sets the volume.
- *
- * @param error_r location to store the error occurring, or
- * NULL to ignore errors
- * @param volume the new volume (0..100 including)
- * @return true on success, false on error
- */
- bool (*set_volume)(struct mixer *mixer, unsigned volume,
- GError **error_r);
-
- /**
- * If true, then the mixer is automatically opened, even if
- * its audio output is not open. If false, then the mixer is
- * disabled as long as its audio output is closed.
- */
- bool global;
-};
-
-#endif
diff --git a/src/mixer_type.c b/src/mixer_type.c
deleted file mode 100644
index a479caf16..000000000
--- a/src/mixer_type.c
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "mixer_type.h"
-
-#include <assert.h>
-#include <string.h>
-
-enum mixer_type
-mixer_type_parse(const char *input)
-{
- assert(input != NULL);
-
- if (strcmp(input, "none") == 0 || strcmp(input, "disabled") == 0)
- return MIXER_TYPE_NONE;
- else if (strcmp(input, "hardware") == 0)
- return MIXER_TYPE_HARDWARE;
- else if (strcmp(input, "software") == 0)
- return MIXER_TYPE_SOFTWARE;
- else
- return MIXER_TYPE_UNKNOWN;
-}
diff --git a/src/mixer_type.h b/src/mixer_type.h
deleted file mode 100644
index 15d136b5b..000000000
--- a/src/mixer_type.h
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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_MIXER_TYPE_H
-#define MPD_MIXER_TYPE_H
-
-enum mixer_type {
- /** parser error */
- MIXER_TYPE_UNKNOWN,
-
- /** mixer disabled */
- MIXER_TYPE_NONE,
-
- /** software mixer with pcm_volume() */
- MIXER_TYPE_SOFTWARE,
-
- /** hardware mixer (output's plugin) */
- MIXER_TYPE_HARDWARE,
-};
-
-/**
- * Parses a "mixer_type" setting from the configuration file.
- *
- * @param input the configured string value; must not be NULL
- * @return a #mixer_type value; MIXER_TYPE_UNKNOWN means #input could
- * not be parsed
- */
-enum mixer_type
-mixer_type_parse(const char *input);
-
-#endif
diff --git a/src/mpd_error.h b/src/mpd_error.h
index 219738ced..e0b7d29a4 100644
--- a/src/mpd_error.h
+++ b/src/mpd_error.h
@@ -20,6 +20,7 @@
#ifndef MPD_ERROR_H
#define MPD_ERROR_H
+#include <glib.h>
#include <stdlib.h>
/* This macro is used as an intermediate step to a proper error handling
diff --git a/src/notify.c b/src/notify.c
deleted file mode 100644
index 3c0112c91..000000000
--- a/src/notify.c
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "notify.h"
-
-void notify_init(struct notify *notify)
-{
- notify->mutex = g_mutex_new();
- notify->cond = g_cond_new();
- notify->pending = false;
-}
-
-void notify_deinit(struct notify *notify)
-{
- g_mutex_free(notify->mutex);
- g_cond_free(notify->cond);
-}
-
-void notify_wait(struct notify *notify)
-{
- g_mutex_lock(notify->mutex);
- while (!notify->pending)
- g_cond_wait(notify->cond, notify->mutex);
- notify->pending = false;
- g_mutex_unlock(notify->mutex);
-}
-
-void notify_signal(struct notify *notify)
-{
- g_mutex_lock(notify->mutex);
- notify->pending = true;
- g_cond_signal(notify->cond);
- g_mutex_unlock(notify->mutex);
-}
-
-void notify_clear(struct notify *notify)
-{
- g_mutex_lock(notify->mutex);
- notify->pending = false;
- g_mutex_unlock(notify->mutex);
-}
diff --git a/src/notify.cxx b/src/notify.cxx
new file mode 100644
index 000000000..64018968c
--- /dev/null
+++ b/src/notify.cxx
@@ -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.
+ */
+
+#include "config.h"
+#include "notify.hxx"
+
+void
+notify::Wait()
+{
+ const ScopeLock protect(mutex);
+ while (!pending)
+ cond.wait(mutex);
+ pending = false;
+}
+
+void
+notify::Signal()
+{
+ const ScopeLock protect(mutex);
+ pending = true;
+ cond.signal();
+}
+
+void
+notify::Clear()
+{
+ const ScopeLock protect(mutex);
+ pending = false;
+}
diff --git a/src/notify.h b/src/notify.h
deleted file mode 100644
index 40821690c..000000000
--- a/src/notify.h
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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_NOTIFY_H
-#define MPD_NOTIFY_H
-
-#include <glib.h>
-
-#include <stdbool.h>
-
-struct notify {
- GMutex *mutex;
- GCond *cond;
- bool pending;
-};
-
-void notify_init(struct notify *notify);
-
-void notify_deinit(struct notify *notify);
-
-/**
- * Wait for a notification. Return immediately if we have already
- * been notified since we last returned from notify_wait().
- */
-void notify_wait(struct notify *notify);
-
-/**
- * Notify the thread. This function never blocks.
- */
-void notify_signal(struct notify *notify);
-
-/**
- * Clears a pending notification.
- */
-void notify_clear(struct notify *notify);
-
-#endif
diff --git a/src/notify.hxx b/src/notify.hxx
new file mode 100644
index 000000000..6b9e95368
--- /dev/null
+++ b/src/notify.hxx
@@ -0,0 +1,53 @@
+/*
+ * 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_NOTIFY_HXX
+#define MPD_NOTIFY_HXX
+
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+
+struct notify {
+ Mutex mutex;
+ Cond cond;
+ bool pending;
+
+#ifndef WIN32
+ constexpr
+#endif
+ notify():pending(false) {}
+
+ /**
+ * Wait for a notification. Return immediately if we have already
+ * been notified since we last returned from notify_wait().
+ */
+ void Wait();
+
+ /**
+ * Notify the thread. This function never blocks.
+ */
+ void Signal();
+
+ /**
+ * Clears a pending notification.
+ */
+ void Clear();
+};
+
+#endif
diff --git a/src/output/AlsaOutputPlugin.cxx b/src/output/AlsaOutputPlugin.cxx
new file mode 100644
index 000000000..b26a3e1df
--- /dev/null
+++ b/src/output/AlsaOutputPlugin.cxx
@@ -0,0 +1,851 @@
+/*
+ * 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 "AlsaOutputPlugin.hxx"
+#include "OutputAPI.hxx"
+#include "MixerList.hxx"
+#include "pcm/PcmExport.hxx"
+#include "util/Manual.hxx"
+
+#include <glib.h>
+#include <alsa/asoundlib.h>
+
+#include <string>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "alsa"
+
+#define ALSA_PCM_NEW_HW_PARAMS_API
+#define ALSA_PCM_NEW_SW_PARAMS_API
+
+static const char default_device[] = "default";
+
+static constexpr unsigned MPD_ALSA_BUFFER_TIME_US = 500000;
+
+#define MPD_ALSA_RETRY_NR 5
+
+typedef snd_pcm_sframes_t alsa_writei_t(snd_pcm_t * pcm, const void *buffer,
+ snd_pcm_uframes_t size);
+
+struct AlsaOutput {
+ struct audio_output base;
+
+ Manual<PcmExport> pcm_export;
+
+ /**
+ * The configured name of the ALSA device; empty for the
+ * default device
+ */
+ std::string device;
+
+ /** use memory mapped I/O? */
+ bool use_mmap;
+
+ /**
+ * Enable DSD over USB according to the dCS suggested
+ * standard?
+ *
+ * @see http://www.dcsltd.co.uk/page/assets/DSDoverUSB.pdf
+ */
+ bool dsd_usb;
+
+ /** libasound's buffer_time setting (in microseconds) */
+ unsigned int buffer_time;
+
+ /** libasound's period_time setting (in microseconds) */
+ unsigned int period_time;
+
+ /** the mode flags passed to snd_pcm_open */
+ int mode;
+
+ /** the libasound PCM device handle */
+ snd_pcm_t *pcm;
+
+ /**
+ * a pointer to the libasound writei() function, which is
+ * snd_pcm_writei() or snd_pcm_mmap_writei(), depending on the
+ * use_mmap configuration
+ */
+ alsa_writei_t *writei;
+
+ /**
+ * The size of one audio frame passed to method play().
+ */
+ size_t in_frame_size;
+
+ /**
+ * The size of one audio frame passed to libasound.
+ */
+ size_t out_frame_size;
+
+ /**
+ * The size of one period, in number of frames.
+ */
+ snd_pcm_uframes_t period_frames;
+
+ /**
+ * The number of frames written in the current period.
+ */
+ snd_pcm_uframes_t period_position;
+
+ /**
+ * This buffer gets allocated after opening the ALSA device.
+ * It contains silence samples, enough to fill one period (see
+ * #period_frames).
+ */
+ void *silence;
+
+ AlsaOutput():mode(0), writei(snd_pcm_writei) {
+ }
+
+ bool Init(const config_param &param, GError **error_r) {
+ return ao_base_init(&base, &alsa_output_plugin,
+ param, error_r);
+ }
+
+ void Deinit() {
+ ao_base_finish(&base);
+ }
+};
+
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+alsa_output_quark(void)
+{
+ return g_quark_from_static_string("alsa_output");
+}
+
+static const char *
+alsa_device(const AlsaOutput *ad)
+{
+ return ad->device.empty() ? default_device : ad->device.c_str();
+}
+
+static void
+alsa_configure(AlsaOutput *ad, const config_param &param)
+{
+ ad->device = param.GetBlockValue("device", "");
+
+ ad->use_mmap = param.GetBlockValue("use_mmap", false);
+
+ ad->dsd_usb = param.GetBlockValue("dsd_usb", false);
+
+ ad->buffer_time = param.GetBlockValue("buffer_time",
+ MPD_ALSA_BUFFER_TIME_US);
+ ad->period_time = param.GetBlockValue("period_time", 0u);
+
+#ifdef SND_PCM_NO_AUTO_RESAMPLE
+ if (!param.GetBlockValue("auto_resample", true))
+ ad->mode |= SND_PCM_NO_AUTO_RESAMPLE;
+#endif
+
+#ifdef SND_PCM_NO_AUTO_CHANNELS
+ if (!param.GetBlockValue("auto_channels", true))
+ ad->mode |= SND_PCM_NO_AUTO_CHANNELS;
+#endif
+
+#ifdef SND_PCM_NO_AUTO_FORMAT
+ if (!param.GetBlockValue("auto_format", true))
+ ad->mode |= SND_PCM_NO_AUTO_FORMAT;
+#endif
+}
+
+static struct audio_output *
+alsa_init(const config_param &param, GError **error_r)
+{
+ AlsaOutput *ad = new AlsaOutput();
+
+ if (!ad->Init(param, error_r)) {
+ delete ad;
+ return NULL;
+ }
+
+ alsa_configure(ad, param);
+
+ return &ad->base;
+}
+
+static void
+alsa_finish(struct audio_output *ao)
+{
+ AlsaOutput *ad = (AlsaOutput *)ao;
+
+ ad->Deinit();
+ delete ad;
+
+ /* free libasound's config cache */
+ snd_config_update_free_global();
+}
+
+static bool
+alsa_output_enable(struct audio_output *ao, G_GNUC_UNUSED GError **error_r)
+{
+ AlsaOutput *ad = (AlsaOutput *)ao;
+
+ ad->pcm_export.Construct();
+ return true;
+}
+
+static void
+alsa_output_disable(struct audio_output *ao)
+{
+ AlsaOutput *ad = (AlsaOutput *)ao;
+
+ ad->pcm_export.Destruct();
+}
+
+static bool
+alsa_test_default_device(void)
+{
+ snd_pcm_t *handle;
+
+ int ret = snd_pcm_open(&handle, default_device,
+ SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
+ if (ret) {
+ g_message("Error opening default ALSA device: %s\n",
+ snd_strerror(-ret));
+ return false;
+ } else
+ snd_pcm_close(handle);
+
+ return true;
+}
+
+static snd_pcm_format_t
+get_bitformat(SampleFormat sample_format)
+{
+ switch (sample_format) {
+ case SampleFormat::UNDEFINED:
+ case SampleFormat::DSD:
+ return SND_PCM_FORMAT_UNKNOWN;
+
+ case SampleFormat::S8:
+ return SND_PCM_FORMAT_S8;
+
+ case SampleFormat::S16:
+ return SND_PCM_FORMAT_S16;
+
+ case SampleFormat::S24_P32:
+ return SND_PCM_FORMAT_S24;
+
+ case SampleFormat::S32:
+ return SND_PCM_FORMAT_S32;
+
+ case SampleFormat::FLOAT:
+ return SND_PCM_FORMAT_FLOAT;
+ }
+
+ assert(false);
+ gcc_unreachable();
+}
+
+static snd_pcm_format_t
+byteswap_bitformat(snd_pcm_format_t fmt)
+{
+ switch(fmt) {
+ case SND_PCM_FORMAT_S16_LE: return SND_PCM_FORMAT_S16_BE;
+ case SND_PCM_FORMAT_S24_LE: return SND_PCM_FORMAT_S24_BE;
+ case SND_PCM_FORMAT_S32_LE: return SND_PCM_FORMAT_S32_BE;
+ case SND_PCM_FORMAT_S16_BE: return SND_PCM_FORMAT_S16_LE;
+ case SND_PCM_FORMAT_S24_BE: return SND_PCM_FORMAT_S24_LE;
+
+ case SND_PCM_FORMAT_S24_3BE:
+ return SND_PCM_FORMAT_S24_3LE;
+
+ case SND_PCM_FORMAT_S24_3LE:
+ return SND_PCM_FORMAT_S24_3BE;
+
+ case SND_PCM_FORMAT_S32_BE: return SND_PCM_FORMAT_S32_LE;
+ default: return SND_PCM_FORMAT_UNKNOWN;
+ }
+}
+
+static snd_pcm_format_t
+alsa_to_packed_format(snd_pcm_format_t fmt)
+{
+ switch (fmt) {
+ case SND_PCM_FORMAT_S24_LE:
+ return SND_PCM_FORMAT_S24_3LE;
+
+ case SND_PCM_FORMAT_S24_BE:
+ return SND_PCM_FORMAT_S24_3BE;
+
+ default:
+ return SND_PCM_FORMAT_UNKNOWN;
+ }
+}
+
+static int
+alsa_try_format_or_packed(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
+ snd_pcm_format_t fmt, bool *packed_r)
+{
+ int err = snd_pcm_hw_params_set_format(pcm, hwparams, fmt);
+ if (err == 0)
+ *packed_r = false;
+
+ if (err != -EINVAL)
+ return err;
+
+ fmt = alsa_to_packed_format(fmt);
+ if (fmt == SND_PCM_FORMAT_UNKNOWN)
+ return -EINVAL;
+
+ err = snd_pcm_hw_params_set_format(pcm, hwparams, fmt);
+ if (err == 0)
+ *packed_r = true;
+
+ return err;
+}
+
+/**
+ * Attempts to configure the specified sample format, and tries the
+ * reversed host byte order if was not supported.
+ */
+static int
+alsa_output_try_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
+ SampleFormat sample_format,
+ bool *packed_r, bool *reverse_endian_r)
+{
+ snd_pcm_format_t alsa_format = get_bitformat(sample_format);
+ if (alsa_format == SND_PCM_FORMAT_UNKNOWN)
+ return -EINVAL;
+
+ int err = alsa_try_format_or_packed(pcm, hwparams, alsa_format,
+ packed_r);
+ if (err == 0)
+ *reverse_endian_r = false;
+
+ if (err != -EINVAL)
+ return err;
+
+ alsa_format = byteswap_bitformat(alsa_format);
+ if (alsa_format == SND_PCM_FORMAT_UNKNOWN)
+ return -EINVAL;
+
+ err = alsa_try_format_or_packed(pcm, hwparams, alsa_format, packed_r);
+ if (err == 0)
+ *reverse_endian_r = true;
+
+ return err;
+}
+
+/**
+ * Configure a sample format, and probe other formats if that fails.
+ */
+static int
+alsa_output_setup_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
+ AudioFormat &audio_format,
+ bool *packed_r, bool *reverse_endian_r)
+{
+ /* try the input format first */
+
+ int err = alsa_output_try_format(pcm, hwparams,
+ audio_format.format,
+ packed_r, reverse_endian_r);
+
+ /* if unsupported by the hardware, try other formats */
+
+ static const SampleFormat probe_formats[] = {
+ SampleFormat::S24_P32,
+ SampleFormat::S32,
+ SampleFormat::S16,
+ SampleFormat::S8,
+ SampleFormat::UNDEFINED,
+ };
+
+ for (unsigned i = 0;
+ err == -EINVAL && probe_formats[i] != SampleFormat::UNDEFINED;
+ ++i) {
+ const SampleFormat mpd_format = probe_formats[i];
+ if (mpd_format == audio_format.format)
+ continue;
+
+ err = alsa_output_try_format(pcm, hwparams, mpd_format,
+ packed_r, reverse_endian_r);
+ if (err == 0)
+ audio_format.format = mpd_format;
+ }
+
+ return err;
+}
+
+/**
+ * Set up the snd_pcm_t object which was opened by the caller. Set up
+ * the configured settings and the audio format.
+ */
+static bool
+alsa_setup(AlsaOutput *ad, AudioFormat &audio_format,
+ bool *packed_r, bool *reverse_endian_r, GError **error)
+{
+ unsigned int sample_rate = audio_format.sample_rate;
+ unsigned int channels = audio_format.channels;
+ int err;
+ const char *cmd = NULL;
+ int retry = MPD_ALSA_RETRY_NR;
+ unsigned int period_time, period_time_ro;
+ unsigned int buffer_time;
+
+ period_time_ro = period_time = ad->period_time;
+configure_hw:
+ /* configure HW params */
+ snd_pcm_hw_params_t *hwparams;
+ snd_pcm_hw_params_alloca(&hwparams);
+ cmd = "snd_pcm_hw_params_any";
+ err = snd_pcm_hw_params_any(ad->pcm, hwparams);
+ if (err < 0)
+ goto error;
+
+ if (ad->use_mmap) {
+ err = snd_pcm_hw_params_set_access(ad->pcm, hwparams,
+ SND_PCM_ACCESS_MMAP_INTERLEAVED);
+ if (err < 0) {
+ g_warning("Cannot set mmap'ed mode on ALSA device \"%s\": %s\n",
+ alsa_device(ad), snd_strerror(-err));
+ g_warning("Falling back to direct write mode\n");
+ ad->use_mmap = false;
+ } else
+ ad->writei = snd_pcm_mmap_writei;
+ }
+
+ if (!ad->use_mmap) {
+ cmd = "snd_pcm_hw_params_set_access";
+ err = snd_pcm_hw_params_set_access(ad->pcm, hwparams,
+ SND_PCM_ACCESS_RW_INTERLEAVED);
+ if (err < 0)
+ goto error;
+ ad->writei = snd_pcm_writei;
+ }
+
+ err = alsa_output_setup_format(ad->pcm, hwparams, audio_format,
+ packed_r, reverse_endian_r);
+ if (err < 0) {
+ g_set_error(error, alsa_output_quark(), err,
+ "ALSA device \"%s\" does not support format %s: %s",
+ alsa_device(ad),
+ sample_format_to_string(audio_format.format),
+ snd_strerror(-err));
+ return false;
+ }
+
+ snd_pcm_format_t format;
+ if (snd_pcm_hw_params_get_format(hwparams, &format) == 0)
+ g_debug("format=%s (%s)", snd_pcm_format_name(format),
+ snd_pcm_format_description(format));
+
+ err = snd_pcm_hw_params_set_channels_near(ad->pcm, hwparams,
+ &channels);
+ if (err < 0) {
+ g_set_error(error, alsa_output_quark(), err,
+ "ALSA device \"%s\" does not support %i channels: %s",
+ alsa_device(ad), (int)audio_format.channels,
+ snd_strerror(-err));
+ return false;
+ }
+ audio_format.channels = (int8_t)channels;
+
+ err = snd_pcm_hw_params_set_rate_near(ad->pcm, hwparams,
+ &sample_rate, NULL);
+ if (err < 0 || sample_rate == 0) {
+ g_set_error(error, alsa_output_quark(), err,
+ "ALSA device \"%s\" does not support %u Hz audio",
+ alsa_device(ad), audio_format.sample_rate);
+ return false;
+ }
+ audio_format.sample_rate = sample_rate;
+
+ snd_pcm_uframes_t buffer_size_min, buffer_size_max;
+ snd_pcm_hw_params_get_buffer_size_min(hwparams, &buffer_size_min);
+ snd_pcm_hw_params_get_buffer_size_max(hwparams, &buffer_size_max);
+ unsigned buffer_time_min, buffer_time_max;
+ snd_pcm_hw_params_get_buffer_time_min(hwparams, &buffer_time_min, 0);
+ snd_pcm_hw_params_get_buffer_time_max(hwparams, &buffer_time_max, 0);
+ g_debug("buffer: size=%u..%u time=%u..%u",
+ (unsigned)buffer_size_min, (unsigned)buffer_size_max,
+ buffer_time_min, buffer_time_max);
+
+ snd_pcm_uframes_t period_size_min, period_size_max;
+ snd_pcm_hw_params_get_period_size_min(hwparams, &period_size_min, 0);
+ snd_pcm_hw_params_get_period_size_max(hwparams, &period_size_max, 0);
+ unsigned period_time_min, period_time_max;
+ snd_pcm_hw_params_get_period_time_min(hwparams, &period_time_min, 0);
+ snd_pcm_hw_params_get_period_time_max(hwparams, &period_time_max, 0);
+ g_debug("period: size=%u..%u time=%u..%u",
+ (unsigned)period_size_min, (unsigned)period_size_max,
+ period_time_min, period_time_max);
+
+ if (ad->buffer_time > 0) {
+ buffer_time = ad->buffer_time;
+ cmd = "snd_pcm_hw_params_set_buffer_time_near";
+ err = snd_pcm_hw_params_set_buffer_time_near(ad->pcm, hwparams,
+ &buffer_time, NULL);
+ if (err < 0)
+ goto error;
+ } else {
+ err = snd_pcm_hw_params_get_buffer_time(hwparams, &buffer_time,
+ NULL);
+ if (err < 0)
+ buffer_time = 0;
+ }
+
+ if (period_time_ro == 0 && buffer_time >= 10000) {
+ period_time_ro = period_time = buffer_time / 4;
+
+ g_debug("default period_time = buffer_time/4 = %u/4 = %u",
+ buffer_time, period_time);
+ }
+
+ if (period_time_ro > 0) {
+ period_time = period_time_ro;
+ cmd = "snd_pcm_hw_params_set_period_time_near";
+ err = snd_pcm_hw_params_set_period_time_near(ad->pcm, hwparams,
+ &period_time, NULL);
+ if (err < 0)
+ goto error;
+ }
+
+ cmd = "snd_pcm_hw_params";
+ err = snd_pcm_hw_params(ad->pcm, hwparams);
+ if (err == -EPIPE && --retry > 0 && period_time_ro > 0) {
+ period_time_ro = period_time_ro >> 1;
+ goto configure_hw;
+ } else if (err < 0)
+ goto error;
+ if (retry != MPD_ALSA_RETRY_NR)
+ g_debug("ALSA period_time set to %d\n", period_time);
+
+ snd_pcm_uframes_t alsa_buffer_size;
+ cmd = "snd_pcm_hw_params_get_buffer_size";
+ err = snd_pcm_hw_params_get_buffer_size(hwparams, &alsa_buffer_size);
+ if (err < 0)
+ goto error;
+
+ snd_pcm_uframes_t alsa_period_size;
+ cmd = "snd_pcm_hw_params_get_period_size";
+ err = snd_pcm_hw_params_get_period_size(hwparams, &alsa_period_size,
+ NULL);
+ if (err < 0)
+ goto error;
+
+ /* configure SW params */
+ snd_pcm_sw_params_t *swparams;
+ snd_pcm_sw_params_alloca(&swparams);
+
+ cmd = "snd_pcm_sw_params_current";
+ err = snd_pcm_sw_params_current(ad->pcm, swparams);
+ if (err < 0)
+ goto error;
+
+ cmd = "snd_pcm_sw_params_set_start_threshold";
+ err = snd_pcm_sw_params_set_start_threshold(ad->pcm, swparams,
+ alsa_buffer_size -
+ alsa_period_size);
+ if (err < 0)
+ goto error;
+
+ cmd = "snd_pcm_sw_params_set_avail_min";
+ err = snd_pcm_sw_params_set_avail_min(ad->pcm, swparams,
+ alsa_period_size);
+ if (err < 0)
+ goto error;
+
+ cmd = "snd_pcm_sw_params";
+ err = snd_pcm_sw_params(ad->pcm, swparams);
+ if (err < 0)
+ goto error;
+
+ g_debug("buffer_size=%u period_size=%u",
+ (unsigned)alsa_buffer_size, (unsigned)alsa_period_size);
+
+ if (alsa_period_size == 0)
+ /* this works around a SIGFPE bug that occurred when
+ an ALSA driver indicated period_size==0; this
+ caused a division by zero in alsa_play(). By using
+ the fallback "1", we make sure that this won't
+ happen again. */
+ alsa_period_size = 1;
+
+ ad->period_frames = alsa_period_size;
+ ad->period_position = 0;
+
+ ad->silence = g_malloc(snd_pcm_frames_to_bytes(ad->pcm,
+ alsa_period_size));
+ snd_pcm_format_set_silence(format, ad->silence,
+ alsa_period_size * channels);
+
+ return true;
+
+error:
+ g_set_error(error, alsa_output_quark(), err,
+ "Error opening ALSA device \"%s\" (%s): %s",
+ alsa_device(ad), cmd, snd_strerror(-err));
+ return false;
+}
+
+static bool
+alsa_setup_dsd(AlsaOutput *ad, const AudioFormat audio_format,
+ bool *shift8_r, bool *packed_r, bool *reverse_endian_r,
+ GError **error_r)
+{
+ assert(ad->dsd_usb);
+ assert(audio_format.format == SampleFormat::DSD);
+
+ /* pass 24 bit to alsa_setup() */
+
+ AudioFormat usb_format = audio_format;
+ usb_format.format = SampleFormat::S24_P32;
+ usb_format.sample_rate /= 2;
+
+ const AudioFormat check = usb_format;
+
+ if (!alsa_setup(ad, usb_format, packed_r, reverse_endian_r, error_r))
+ return false;
+
+ /* if the device allows only 32 bit, shift all DSD-over-USB
+ samples left by 8 bit and leave the lower 8 bit cleared;
+ the DSD-over-USB documentation does not specify whether
+ this is legal, but there is anecdotical evidence that this
+ is possible (and the only option for some devices) */
+ *shift8_r = usb_format.format == SampleFormat::S32;
+ if (usb_format.format == SampleFormat::S32)
+ usb_format.format = SampleFormat::S24_P32;
+
+ if (usb_format != check) {
+ /* no bit-perfect playback, which is required
+ for DSD over USB */
+ g_set_error(error_r, alsa_output_quark(), 0,
+ "Failed to configure DSD-over-USB on ALSA device \"%s\"",
+ alsa_device(ad));
+ g_free(ad->silence);
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+alsa_setup_or_dsd(AlsaOutput *ad, AudioFormat &audio_format,
+ GError **error_r)
+{
+ bool shift8 = false, packed, reverse_endian;
+
+ const bool dsd_usb = ad->dsd_usb &&
+ audio_format.format == SampleFormat::DSD;
+ const bool success = dsd_usb
+ ? alsa_setup_dsd(ad, audio_format,
+ &shift8, &packed, &reverse_endian,
+ error_r)
+ : alsa_setup(ad, audio_format, &packed, &reverse_endian,
+ error_r);
+ if (!success)
+ return false;
+
+ ad->pcm_export->Open(audio_format.format,
+ audio_format.channels,
+ dsd_usb, shift8, packed, reverse_endian);
+ return true;
+}
+
+static bool
+alsa_open(struct audio_output *ao, AudioFormat &audio_format, GError **error)
+{
+ AlsaOutput *ad = (AlsaOutput *)ao;
+
+ int err = snd_pcm_open(&ad->pcm, alsa_device(ad),
+ SND_PCM_STREAM_PLAYBACK, ad->mode);
+ if (err < 0) {
+ g_set_error(error, alsa_output_quark(), err,
+ "Failed to open ALSA device \"%s\": %s",
+ alsa_device(ad), snd_strerror(err));
+ return false;
+ }
+
+ g_debug("opened %s type=%s", snd_pcm_name(ad->pcm),
+ snd_pcm_type_name(snd_pcm_type(ad->pcm)));
+
+ if (!alsa_setup_or_dsd(ad, audio_format, error)) {
+ snd_pcm_close(ad->pcm);
+ return false;
+ }
+
+ ad->in_frame_size = audio_format.GetFrameSize();
+ ad->out_frame_size = ad->pcm_export->GetFrameSize(audio_format);
+
+ return true;
+}
+
+/**
+ * Write silence to the ALSA device.
+ */
+static void
+alsa_write_silence(AlsaOutput *ad, snd_pcm_uframes_t nframes)
+{
+ ad->writei(ad->pcm, ad->silence, nframes);
+}
+
+static int
+alsa_recover(AlsaOutput *ad, int err)
+{
+ if (err == -EPIPE) {
+ g_debug("Underrun on ALSA device \"%s\"\n", alsa_device(ad));
+ } else if (err == -ESTRPIPE) {
+ g_debug("ALSA device \"%s\" was suspended\n", alsa_device(ad));
+ }
+
+ switch (snd_pcm_state(ad->pcm)) {
+ case SND_PCM_STATE_PAUSED:
+ err = snd_pcm_pause(ad->pcm, /* disable */ 0);
+ break;
+ case SND_PCM_STATE_SUSPENDED:
+ err = snd_pcm_resume(ad->pcm);
+ if (err == -EAGAIN)
+ return 0;
+ /* fall-through to snd_pcm_prepare: */
+ case SND_PCM_STATE_SETUP:
+ case SND_PCM_STATE_XRUN:
+ ad->period_position = 0;
+ err = snd_pcm_prepare(ad->pcm);
+
+ if (err == 0) {
+ /* this works around a driver bug observed on
+ the Raspberry Pi: after snd_pcm_drop(), the
+ whole ring buffer must be invalidated, but
+ the snd_pcm_prepare() call above makes the
+ driver play random data that just happens
+ to be still in the buffer; by adding and
+ cancelling some silence, this bug does not
+ occur */
+ alsa_write_silence(ad, ad->period_frames);
+
+ /* cancel the silence data right away to avoid
+ increasing latency; even though this
+ function call invalidates the portion of
+ silence, the driver seems to avoid the
+ bug */
+ snd_pcm_reset(ad->pcm);
+ }
+
+ break;
+ case SND_PCM_STATE_DISCONNECTED:
+ break;
+ /* this is no error, so just keep running */
+ case SND_PCM_STATE_RUNNING:
+ err = 0;
+ break;
+ default:
+ /* unknown state, do nothing */
+ break;
+ }
+
+ return err;
+}
+
+static void
+alsa_drain(struct audio_output *ao)
+{
+ AlsaOutput *ad = (AlsaOutput *)ao;
+
+ if (snd_pcm_state(ad->pcm) != SND_PCM_STATE_RUNNING)
+ return;
+
+ if (ad->period_position > 0) {
+ /* generate some silence to finish the partial
+ period */
+ snd_pcm_uframes_t nframes =
+ ad->period_frames - ad->period_position;
+ alsa_write_silence(ad, nframes);
+ }
+
+ snd_pcm_drain(ad->pcm);
+
+ ad->period_position = 0;
+}
+
+static void
+alsa_cancel(struct audio_output *ao)
+{
+ AlsaOutput *ad = (AlsaOutput *)ao;
+
+ ad->period_position = 0;
+
+ snd_pcm_drop(ad->pcm);
+}
+
+static void
+alsa_close(struct audio_output *ao)
+{
+ AlsaOutput *ad = (AlsaOutput *)ao;
+
+ snd_pcm_close(ad->pcm);
+ g_free(ad->silence);
+}
+
+static size_t
+alsa_play(struct audio_output *ao, const void *chunk, size_t size,
+ GError **error)
+{
+ AlsaOutput *ad = (AlsaOutput *)ao;
+
+ assert(size % ad->in_frame_size == 0);
+
+ chunk = ad->pcm_export->Export(chunk, size, size);
+
+ assert(size % ad->out_frame_size == 0);
+
+ size /= ad->out_frame_size;
+
+ while (true) {
+ snd_pcm_sframes_t ret = ad->writei(ad->pcm, chunk, size);
+ if (ret > 0) {
+ ad->period_position = (ad->period_position + ret)
+ % ad->period_frames;
+
+ size_t bytes_written = ret * ad->out_frame_size;
+ return ad->pcm_export->CalcSourceSize(bytes_written);
+ }
+
+ if (ret < 0 && ret != -EAGAIN && ret != -EINTR &&
+ alsa_recover(ad, ret) < 0) {
+ g_set_error(error, alsa_output_quark(), errno,
+ "%s", snd_strerror(-errno));
+ return 0;
+ }
+ }
+}
+
+const struct audio_output_plugin alsa_output_plugin = {
+ "alsa",
+ alsa_test_default_device,
+ alsa_init,
+ alsa_finish,
+ alsa_output_enable,
+ alsa_output_disable,
+ alsa_open,
+ alsa_close,
+ nullptr,
+ nullptr,
+ alsa_play,
+ alsa_drain,
+ alsa_cancel,
+ nullptr,
+
+ &alsa_mixer_plugin,
+};
diff --git a/src/output/AlsaOutputPlugin.hxx b/src/output/AlsaOutputPlugin.hxx
new file mode 100644
index 000000000..dc7e639a8
--- /dev/null
+++ b/src/output/AlsaOutputPlugin.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_ALSA_OUTPUT_PLUGIN_HXX
+#define MPD_ALSA_OUTPUT_PLUGIN_HXX
+
+extern const struct audio_output_plugin alsa_output_plugin;
+
+#endif
diff --git a/src/output/AoOutputPlugin.cxx b/src/output/AoOutputPlugin.cxx
new file mode 100644
index 000000000..db7b9a360
--- /dev/null
+++ b/src/output/AoOutputPlugin.cxx
@@ -0,0 +1,291 @@
+/*
+ * 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 "AoOutputPlugin.hxx"
+#include "OutputAPI.hxx"
+
+#include <ao/ao.h>
+#include <glib.h>
+
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "ao"
+
+/* An ao_sample_format, with all fields set to zero: */
+static ao_sample_format OUR_AO_FORMAT_INITIALIZER;
+
+static unsigned ao_output_ref;
+
+struct AoOutput {
+ struct audio_output base;
+
+ size_t write_size;
+ int driver;
+ ao_option *options;
+ ao_device *device;
+
+ bool Initialize(const config_param &param, GError **error_r) {
+ return ao_base_init(&base, &ao_output_plugin, param,
+ error_r);
+ }
+
+ void Deinitialize() {
+ ao_base_finish(&base);
+ }
+
+ bool Configure(const config_param &param, GError **error_r);
+};
+
+static inline GQuark
+ao_output_quark(void)
+{
+ return g_quark_from_static_string("ao_output");
+}
+
+static void
+ao_output_error(GError **error_r)
+{
+ const char *error;
+
+ switch (errno) {
+ case AO_ENODRIVER:
+ error = "No such libao driver";
+ break;
+
+ case AO_ENOTLIVE:
+ error = "This driver is not a libao live device";
+ break;
+
+ case AO_EBADOPTION:
+ error = "Invalid libao option";
+ break;
+
+ case AO_EOPENDEVICE:
+ error = "Cannot open the libao device";
+ break;
+
+ case AO_EFAIL:
+ error = "Generic libao failure";
+ break;
+
+ default:
+ error = g_strerror(errno);
+ }
+
+ g_set_error(error_r, ao_output_quark(), errno,
+ "%s", error);
+}
+
+inline bool
+AoOutput::Configure(const config_param &param, GError **error_r)
+{
+ const char *value;
+
+ options = nullptr;
+
+ write_size = param.GetBlockValue("write_size", 1024u);
+
+ if (ao_output_ref == 0) {
+ ao_initialize();
+ }
+ ao_output_ref++;
+
+ value = param.GetBlockValue("driver", "default");
+ if (0 == strcmp(value, "default"))
+ driver = ao_default_driver_id();
+ else
+ driver = ao_driver_id(value);
+
+ if (driver < 0) {
+ g_set_error(error_r, ao_output_quark(), 0,
+ "\"%s\" is not a valid ao driver",
+ value);
+ return false;
+ }
+
+ ao_info *ai = ao_driver_info(driver);
+ if (ai == nullptr) {
+ g_set_error(error_r, ao_output_quark(), 0,
+ "problems getting driver info");
+ return false;
+ }
+
+ g_debug("using ao driver \"%s\" for \"%s\"\n", ai->short_name,
+ param.GetBlockValue("name", nullptr));
+
+ value = param.GetBlockValue("options", nullptr);
+ if (value != nullptr) {
+ gchar **_options = g_strsplit(value, ";", 0);
+
+ for (unsigned i = 0; _options[i] != nullptr; ++i) {
+ gchar **key_value = g_strsplit(_options[i], "=", 2);
+
+ if (key_value[0] == nullptr || key_value[1] == nullptr) {
+ g_set_error(error_r, ao_output_quark(), 0,
+ "problems parsing options \"%s\"",
+ _options[i]);
+ return false;
+ }
+
+ ao_append_option(&options, key_value[0],
+ key_value[1]);
+
+ g_strfreev(key_value);
+ }
+
+ g_strfreev(_options);
+ }
+
+ return true;
+}
+
+static struct audio_output *
+ao_output_init(const config_param &param, GError **error_r)
+{
+ AoOutput *ad = new AoOutput();
+
+ if (!ad->Initialize(param, error_r)) {
+ delete ad;
+ return nullptr;
+ }
+
+ if (!ad->Configure(param, error_r)) {
+ ad->Deinitialize();
+ delete ad;
+ return nullptr;
+ }
+
+ return &ad->base;
+}
+
+static void
+ao_output_finish(struct audio_output *ao)
+{
+ AoOutput *ad = (AoOutput *)ao;
+
+ ao_free_options(ad->options);
+ ad->Deinitialize();
+ delete ad;
+
+ ao_output_ref--;
+
+ if (ao_output_ref == 0)
+ ao_shutdown();
+}
+
+static void
+ao_output_close(struct audio_output *ao)
+{
+ AoOutput *ad = (AoOutput *)ao;
+
+ ao_close(ad->device);
+}
+
+static bool
+ao_output_open(struct audio_output *ao, AudioFormat &audio_format,
+ GError **error)
+{
+ ao_sample_format format = OUR_AO_FORMAT_INITIALIZER;
+ AoOutput *ad = (AoOutput *)ao;
+
+ switch (audio_format.format) {
+ case SampleFormat::S8:
+ format.bits = 8;
+ break;
+
+ case SampleFormat::S16:
+ format.bits = 16;
+ break;
+
+ default:
+ /* support for 24 bit samples in libao is currently
+ dubious, and until we have sorted that out,
+ convert everything to 16 bit */
+ audio_format.format = SampleFormat::S16;
+ format.bits = 16;
+ break;
+ }
+
+ format.rate = audio_format.sample_rate;
+ format.byte_format = AO_FMT_NATIVE;
+ format.channels = audio_format.channels;
+
+ ad->device = ao_open_live(ad->driver, &format, ad->options);
+
+ if (ad->device == nullptr) {
+ ao_output_error(error);
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * For whatever reason, libao wants a non-const pointer. Let's hope
+ * it does not write to the buffer, and use the union deconst hack to
+ * work around this API misdesign.
+ */
+static int ao_play_deconst(ao_device *device, const void *output_samples,
+ uint_32 num_bytes)
+{
+ union {
+ const void *in;
+ char *out;
+ } u;
+
+ u.in = output_samples;
+ return ao_play(device, u.out, num_bytes);
+}
+
+static size_t
+ao_output_play(struct audio_output *ao, const void *chunk, size_t size,
+ GError **error)
+{
+ AoOutput *ad = (AoOutput *)ao;
+
+ if (size > ad->write_size)
+ size = ad->write_size;
+
+ if (ao_play_deconst(ad->device, chunk, size) == 0) {
+ ao_output_error(error);
+ return 0;
+ }
+
+ return size;
+}
+
+const struct audio_output_plugin ao_output_plugin = {
+ "ao",
+ nullptr,
+ ao_output_init,
+ ao_output_finish,
+ nullptr,
+ nullptr,
+ ao_output_open,
+ ao_output_close,
+ nullptr,
+ nullptr,
+ ao_output_play,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+};
diff --git a/src/output/AoOutputPlugin.hxx b/src/output/AoOutputPlugin.hxx
new file mode 100644
index 000000000..a44885e56
--- /dev/null
+++ b/src/output/AoOutputPlugin.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_AO_OUTPUT_PLUGIN_HXX
+#define MPD_AO_OUTPUT_PLUGIN_HXX
+
+extern const struct audio_output_plugin ao_output_plugin;
+
+#endif
diff --git a/src/output/FifoOutputPlugin.cxx b/src/output/FifoOutputPlugin.cxx
new file mode 100644
index 000000000..50062988c
--- /dev/null
+++ b/src/output/FifoOutputPlugin.cxx
@@ -0,0 +1,326 @@
+/*
+ * 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 "FifoOutputPlugin.hxx"
+#include "OutputAPI.hxx"
+#include "Timer.hxx"
+#include "fd_util.h"
+#include "open.h"
+
+#include <glib.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "fifo"
+
+#define FIFO_BUFFER_SIZE 65536 /* pipe capacity on Linux >= 2.6.11 */
+
+struct FifoOutput {
+ struct audio_output base;
+
+ char *path;
+ int input;
+ int output;
+ bool created;
+ Timer *timer;
+
+ FifoOutput()
+ :path(nullptr), input(-1), output(-1), created(false) {}
+
+ ~FifoOutput() {
+ g_free(path);
+ }
+
+ bool Initialize(const config_param &param, GError **error_r) {
+ return ao_base_init(&base, &fifo_output_plugin, param,
+ error_r);
+ }
+
+ void Deinitialize() {
+ ao_base_finish(&base);
+ }
+
+ bool Create(GError **error_r);
+ bool Check(GError **error_r);
+ void Delete();
+
+ bool Open(GError **error_r);
+ void Close();
+};
+
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+fifo_output_quark(void)
+{
+ return g_quark_from_static_string("fifo_output");
+}
+
+inline void
+FifoOutput::Delete()
+{
+ g_debug("Removing FIFO \"%s\"", path);
+
+ if (unlink(path) < 0) {
+ g_warning("Could not remove FIFO \"%s\": %s",
+ path, g_strerror(errno));
+ return;
+ }
+
+ created = false;
+}
+
+void
+FifoOutput::Close()
+{
+ struct stat st;
+
+ if (input >= 0) {
+ close(input);
+ input = -1;
+ }
+
+ if (output >= 0) {
+ close(output);
+ output = -1;
+ }
+
+ if (created && (stat(path, &st) == 0))
+ Delete();
+}
+
+inline bool
+FifoOutput::Create(GError **error_r)
+{
+ if (mkfifo(path, 0666) < 0) {
+ g_set_error(error_r, fifo_output_quark(), errno,
+ "Couldn't create FIFO \"%s\": %s",
+ path, g_strerror(errno));
+ return false;
+ }
+
+ created = true;
+ return true;
+}
+
+inline bool
+FifoOutput::Check(GError **error_r)
+{
+ struct stat st;
+ if (stat(path, &st) < 0) {
+ if (errno == ENOENT) {
+ /* Path doesn't exist */
+ return Create(error_r);
+ }
+
+ g_set_error(error_r, fifo_output_quark(), errno,
+ "Failed to stat FIFO \"%s\": %s",
+ path, g_strerror(errno));
+ return false;
+ }
+
+ if (!S_ISFIFO(st.st_mode)) {
+ g_set_error(error_r, fifo_output_quark(), 0,
+ "\"%s\" already exists, but is not a FIFO",
+ path);
+ return false;
+ }
+
+ return true;
+}
+
+inline bool
+FifoOutput::Open(GError **error_r)
+{
+ if (!Check(error_r))
+ return false;
+
+ input = open_cloexec(path, O_RDONLY|O_NONBLOCK|O_BINARY, 0);
+ if (input < 0) {
+ g_set_error(error_r, fifo_output_quark(), errno,
+ "Could not open FIFO \"%s\" for reading: %s",
+ path, g_strerror(errno));
+ Close();
+ return false;
+ }
+
+ output = open_cloexec(path, O_WRONLY|O_NONBLOCK|O_BINARY, 0);
+ if (output < 0) {
+ g_set_error(error_r, fifo_output_quark(), errno,
+ "Could not open FIFO \"%s\" for writing: %s",
+ path, g_strerror(errno));
+ Close();
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+fifo_open(FifoOutput *fd, GError **error_r)
+{
+ return fd->Open(error_r);
+}
+
+static struct audio_output *
+fifo_output_init(const config_param &param, GError **error_r)
+{
+ GError *error = nullptr;
+ char *path = param.DupBlockPath("path", &error);
+ if (!path) {
+ if (error != nullptr)
+ g_propagate_error(error_r, error);
+ else
+ g_set_error(error_r, fifo_output_quark(), 0,
+ "No \"path\" parameter specified");
+ return nullptr;
+ }
+
+ FifoOutput *fd = new FifoOutput();
+ fd->path = path;
+
+ if (!fd->Initialize(param, error_r)) {
+ delete fd;
+ return nullptr;
+ }
+
+ if (!fifo_open(fd, error_r)) {
+ fd->Deinitialize();
+ delete fd;
+ return nullptr;
+ }
+
+ return &fd->base;
+}
+
+static void
+fifo_output_finish(struct audio_output *ao)
+{
+ FifoOutput *fd = (FifoOutput *)ao;
+
+ fd->Close();
+ fd->Deinitialize();
+ delete fd;
+}
+
+static bool
+fifo_output_open(struct audio_output *ao, AudioFormat &audio_format,
+ G_GNUC_UNUSED GError **error)
+{
+ FifoOutput *fd = (FifoOutput *)ao;
+
+ fd->timer = new Timer(audio_format);
+
+ return true;
+}
+
+static void
+fifo_output_close(struct audio_output *ao)
+{
+ FifoOutput *fd = (FifoOutput *)ao;
+
+ delete fd->timer;
+}
+
+static void
+fifo_output_cancel(struct audio_output *ao)
+{
+ FifoOutput *fd = (FifoOutput *)ao;
+ char buf[FIFO_BUFFER_SIZE];
+ int bytes = 1;
+
+ fd->timer->Reset();
+
+ while (bytes > 0 && errno != EINTR)
+ bytes = read(fd->input, buf, FIFO_BUFFER_SIZE);
+
+ if (bytes < 0 && errno != EAGAIN) {
+ g_warning("Flush of FIFO \"%s\" failed: %s",
+ fd->path, g_strerror(errno));
+ }
+}
+
+static unsigned
+fifo_output_delay(struct audio_output *ao)
+{
+ FifoOutput *fd = (FifoOutput *)ao;
+
+ return fd->timer->IsStarted()
+ ? fd->timer->GetDelay()
+ : 0;
+}
+
+static size_t
+fifo_output_play(struct audio_output *ao, const void *chunk, size_t size,
+ GError **error)
+{
+ FifoOutput *fd = (FifoOutput *)ao;
+ ssize_t bytes;
+
+ if (!fd->timer->IsStarted())
+ fd->timer->Start();
+ fd->timer->Add(size);
+
+ while (true) {
+ bytes = write(fd->output, chunk, size);
+ if (bytes > 0)
+ return (size_t)bytes;
+
+ if (bytes < 0) {
+ switch (errno) {
+ case EAGAIN:
+ /* The pipe is full, so empty it */
+ fifo_output_cancel(&fd->base);
+ continue;
+ case EINTR:
+ continue;
+ }
+
+ g_set_error(error, fifo_output_quark(), errno,
+ "Failed to write to FIFO %s: %s",
+ fd->path, g_strerror(errno));
+ return 0;
+ }
+ }
+}
+
+const struct audio_output_plugin fifo_output_plugin = {
+ "fifo",
+ nullptr,
+ fifo_output_init,
+ fifo_output_finish,
+ nullptr,
+ nullptr,
+ fifo_output_open,
+ fifo_output_close,
+ fifo_output_delay,
+ nullptr,
+ fifo_output_play,
+ nullptr,
+ fifo_output_cancel,
+ nullptr,
+ nullptr,
+};
diff --git a/src/output/FifoOutputPlugin.hxx b/src/output/FifoOutputPlugin.hxx
new file mode 100644
index 000000000..dca2886d8
--- /dev/null
+++ b/src/output/FifoOutputPlugin.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_FIFO_OUTPUT_PLUGIN_HXX
+#define MPD_FIFO_OUTPUT_PLUGIN_HXX
+
+extern const struct audio_output_plugin fifo_output_plugin;
+
+#endif
diff --git a/src/output/HttpdClient.cxx b/src/output/HttpdClient.cxx
new file mode 100644
index 000000000..0a00ee2f9
--- /dev/null
+++ b/src/output/HttpdClient.cxx
@@ -0,0 +1,444 @@
+/*
+ * 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.
+ */
+
+#include "config.h"
+#include "HttpdClient.hxx"
+#include "HttpdInternal.hxx"
+#include "util/fifo_buffer.h"
+#include "Page.hxx"
+#include "IcyMetaDataServer.hxx"
+#include "SocketError.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "httpd_output"
+
+HttpdClient::~HttpdClient()
+{
+ if (state == RESPONSE) {
+ if (current_page != nullptr)
+ current_page->Unref();
+
+ for (auto page : pages)
+ page->Unref();
+ }
+
+ if (metadata)
+ metadata->Unref();
+}
+
+void
+HttpdClient::Close()
+{
+ httpd->RemoveClient(*this);
+}
+
+void
+HttpdClient::LockClose()
+{
+ const ScopeLock protect(httpd->mutex);
+ Close();
+}
+
+void
+HttpdClient::BeginResponse()
+{
+ assert(state != RESPONSE);
+
+ state = RESPONSE;
+ current_page = nullptr;
+
+ httpd->SendHeader(*this);
+}
+
+/**
+ * Handle a line of the HTTP request.
+ */
+bool
+HttpdClient::HandleLine(const char *line)
+{
+ assert(state != RESPONSE);
+
+ if (state == REQUEST) {
+ if (strncmp(line, "GET /", 5) != 0) {
+ /* only GET is supported */
+ g_warning("malformed request line from client");
+ return false;
+ }
+
+ line = strchr(line + 5, ' ');
+ if (line == nullptr || strncmp(line + 1, "HTTP/", 5) != 0) {
+ /* HTTP/0.9 without request headers */
+ BeginResponse();
+ return true;
+ }
+
+ /* after the request line, request headers follow */
+ state = HEADERS;
+ return true;
+ } else {
+ if (*line == 0) {
+ /* empty line: request is finished */
+ BeginResponse();
+ return true;
+ }
+
+ if (g_ascii_strncasecmp(line, "Icy-MetaData: 1", 15) == 0) {
+ /* Send icy metadata */
+ metadata_requested = metadata_supported;
+ return true;
+ }
+
+ if (g_ascii_strncasecmp(line, "transferMode.dlna.org: Streaming", 32) == 0) {
+ /* Send as dlna */
+ dlna_streaming_requested = true;
+ /* metadata is not supported by dlna streaming, so disable it */
+ metadata_supported = false;
+ metadata_requested = false;
+ return true;
+ }
+
+ /* expect more request headers */
+ return true;
+ }
+}
+
+/**
+ * Sends the status line and response headers to the client.
+ */
+bool
+HttpdClient::SendResponse()
+{
+ char buffer[1024];
+ assert(state == RESPONSE);
+
+ if (dlna_streaming_requested) {
+ g_snprintf(buffer, sizeof(buffer),
+ "HTTP/1.1 206 OK\r\n"
+ "Content-Type: %s\r\n"
+ "Content-Length: 10000\r\n"
+ "Content-RangeX: 0-1000000/1000000\r\n"
+ "transferMode.dlna.org: Streaming\r\n"
+ "Accept-Ranges: bytes\r\n"
+ "Connection: close\r\n"
+ "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
+ "contentFeatures.dlna.org: DLNA.ORG_OP=01;DLNA.ORG_CI=0\r\n"
+ "\r\n",
+ httpd->content_type);
+
+ } else if (metadata_requested) {
+ gchar *metadata_header;
+
+ metadata_header =
+ icy_server_metadata_header(httpd->name, httpd->genre,
+ httpd->website,
+ httpd->content_type,
+ metaint);
+
+ g_strlcpy(buffer, metadata_header, sizeof(buffer));
+
+ g_free(metadata_header);
+
+ } else { /* revert to a normal HTTP request */
+ g_snprintf(buffer, sizeof(buffer),
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Type: %s\r\n"
+ "Connection: close\r\n"
+ "Pragma: no-cache\r\n"
+ "Cache-Control: no-cache, no-store\r\n"
+ "\r\n",
+ httpd->content_type);
+ }
+
+ ssize_t nbytes = SocketMonitor::Write(buffer, strlen(buffer));
+ if (gcc_unlikely(nbytes < 0)) {
+ const SocketErrorMessage msg;
+ g_warning("failed to write to client: %s", (const char *)msg);
+ Close();
+ return false;
+ }
+
+ return true;
+}
+
+HttpdClient::HttpdClient(HttpdOutput *_httpd, int _fd, EventLoop &_loop,
+ bool _metadata_supported)
+ :BufferedSocket(_fd, _loop),
+ httpd(_httpd),
+ state(REQUEST),
+ dlna_streaming_requested(false),
+ metadata_supported(_metadata_supported),
+ metadata_requested(false), metadata_sent(true),
+ metaint(8192), /*TODO: just a std value */
+ metadata(nullptr),
+ metadata_current_position(0), metadata_fill(0)
+{
+}
+
+size_t
+HttpdClient::GetQueueSize() const
+{
+ if (state != RESPONSE)
+ return 0;
+
+ size_t size = 0;
+ for (auto page : pages)
+ size += page->size;
+ return size;
+}
+
+void
+HttpdClient::CancelQueue()
+{
+ if (state != RESPONSE)
+ return;
+
+ for (auto page : pages)
+ page->Unref();
+ pages.clear();
+
+ if (current_page == nullptr)
+ CancelWrite();
+}
+
+ssize_t
+HttpdClient::TryWritePage(const Page &page, size_t position)
+{
+ assert(position < page.size);
+
+ return Write(page.data + position, page.size - position);
+}
+
+ssize_t
+HttpdClient::TryWritePageN(const Page &page, size_t position, ssize_t n)
+{
+ return n >= 0
+ ? Write(page.data + position, n)
+ : TryWritePage(page, position);
+}
+
+ssize_t
+HttpdClient::GetBytesTillMetaData() const
+{
+ if (metadata_requested &&
+ current_page->size - current_position > metaint - metadata_fill)
+ return metaint - metadata_fill;
+
+ return -1;
+}
+
+inline bool
+HttpdClient::TryWrite()
+{
+ const ScopeLock protect(httpd->mutex);
+
+ assert(state == RESPONSE);
+
+ if (current_page == nullptr) {
+ if (pages.empty()) {
+ /* another thread has removed the event source
+ while this thread was waiting for
+ httpd->mutex */
+ CancelWrite();
+ return true;
+ }
+
+ current_page = pages.front();
+ pages.pop_front();
+ current_position = 0;
+ }
+
+ const ssize_t bytes_to_write = GetBytesTillMetaData();
+ if (bytes_to_write == 0) {
+ if (!metadata_sent) {
+ ssize_t nbytes = TryWritePage(*metadata,
+ metadata_current_position);
+ if (nbytes < 0) {
+ auto e = GetSocketError();
+ if (IsSocketErrorAgain(e))
+ return true;
+
+ if (!IsSocketErrorClosed(e)) {
+ SocketErrorMessage msg(e);
+ g_warning("failed to write to client: %s",
+ (const char *)msg);
+ }
+
+ Close();
+ return false;
+ }
+
+ metadata_current_position += nbytes;
+
+ if (metadata->size - metadata_current_position == 0) {
+ metadata_fill = 0;
+ metadata_current_position = 0;
+ metadata_sent = true;
+ }
+ } else {
+ guchar empty_data = 0;
+
+ ssize_t nbytes = Write(&empty_data, 1);
+ if (nbytes < 0) {
+ auto e = GetSocketError();
+ if (IsSocketErrorAgain(e))
+ return true;
+
+ if (!IsSocketErrorClosed(e)) {
+ SocketErrorMessage msg(e);
+ g_warning("failed to write to client: %s",
+ (const char *)msg);
+ }
+
+ Close();
+ return false;
+ }
+
+ metadata_fill = 0;
+ metadata_current_position = 0;
+ }
+ } else {
+ ssize_t nbytes =
+ TryWritePageN(*current_page, current_position,
+ bytes_to_write);
+ if (nbytes < 0) {
+ auto e = GetSocketError();
+ if (IsSocketErrorAgain(e))
+ return true;
+
+ if (!IsSocketErrorClosed(e)) {
+ SocketErrorMessage msg(e);
+ g_warning("failed to write to client: %s",
+ (const char *)msg);
+ }
+
+ Close();
+ return false;
+ }
+
+ current_position += nbytes;
+ assert(current_position <= current_page->size);
+
+ if (metadata_requested)
+ metadata_fill += nbytes;
+
+ if (current_position >= current_page->size) {
+ current_page->Unref();
+ current_page = nullptr;
+
+ if (pages.empty())
+ /* all pages are sent: remove the
+ event source */
+ CancelWrite();
+ }
+ }
+
+ return true;
+}
+
+void
+HttpdClient::PushPage(Page *page)
+{
+ if (state != RESPONSE)
+ /* the client is still writing the HTTP request */
+ return;
+
+ page->Ref();
+ pages.push_back(page);
+
+ ScheduleWrite();
+}
+
+void
+HttpdClient::PushMetaData(Page *page)
+{
+ if (metadata) {
+ metadata->Unref();
+ metadata = nullptr;
+ }
+
+ g_return_if_fail (page);
+
+ page->Ref();
+ metadata = page;
+ metadata_sent = false;
+}
+
+bool
+HttpdClient::OnSocketReady(unsigned flags)
+{
+ if (!BufferedSocket::OnSocketReady(flags))
+ return false;
+
+ if (flags & WRITE)
+ if (!TryWrite())
+ return false;
+
+ return true;
+}
+
+BufferedSocket::InputResult
+HttpdClient::OnSocketInput(const void *data, size_t length)
+{
+ if (state == RESPONSE) {
+ g_warning("unexpected input from client");
+ LockClose();
+ return InputResult::CLOSED;
+ }
+
+ const char *line = (const char *)data;
+ const char *newline = (const char *)memchr(line, '\n', length);
+ if (newline == nullptr)
+ return InputResult::MORE;
+
+ ConsumeInput(newline + 1 - line);
+
+ if (newline > line && newline[-1] == '\r')
+ --newline;
+
+ /* terminate the string at the end of the line; the const_cast
+ is a dirty hack */
+ *const_cast<char *>(newline) = 0;
+
+ if (!HandleLine(line)) {
+ assert(state == RESPONSE);
+ LockClose();
+ return InputResult::CLOSED;
+ }
+
+ if (state == RESPONSE && !SendResponse())
+ return InputResult::CLOSED;
+
+ return InputResult::AGAIN;
+}
+
+void
+HttpdClient::OnSocketError(GError *error)
+{
+ g_warning("error on HTTP client: %s", error->message);
+ g_error_free(error);
+}
+
+void
+HttpdClient::OnSocketClosed()
+{
+ LockClose();
+}
diff --git a/src/output/HttpdClient.hxx b/src/output/HttpdClient.hxx
new file mode 100644
index 000000000..46196d2e3
--- /dev/null
+++ b/src/output/HttpdClient.hxx
@@ -0,0 +1,186 @@
+/*
+ * 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_OUTPUT_HTTPD_CLIENT_HXX
+#define MPD_OUTPUT_HTTPD_CLIENT_HXX
+
+#include "event/BufferedSocket.hxx"
+#include "gcc.h"
+
+#include <list>
+
+#include <stddef.h>
+
+struct HttpdOutput;
+class Page;
+
+class HttpdClient final : public BufferedSocket {
+ /**
+ * The httpd output object this client is connected to.
+ */
+ HttpdOutput *const httpd;
+
+ /**
+ * The current state of the client.
+ */
+ enum {
+ /** reading the request line */
+ REQUEST,
+
+ /** reading the request headers */
+ HEADERS,
+
+ /** sending the HTTP response */
+ RESPONSE,
+ } state;
+
+ /**
+ * A queue of #Page objects to be sent to the client.
+ */
+ std::list<Page *> pages;
+
+ /**
+ * The #page which is currently being sent to the client.
+ */
+ Page *current_page;
+
+ /**
+ * The amount of bytes which were already sent from
+ * #current_page.
+ */
+ size_t current_position;
+
+ /**
+ * If DLNA streaming was an option.
+ */
+ bool dlna_streaming_requested;
+
+ /* ICY */
+
+ /**
+ * Do we support sending Icy-Metadata to the client? This is
+ * disabled if the httpd audio output uses encoder tags.
+ */
+ bool metadata_supported;
+
+ /**
+ * If we should sent icy metadata.
+ */
+ bool metadata_requested;
+
+ /**
+ * If the current metadata was already sent to the client.
+ */
+ bool metadata_sent;
+
+ /**
+ * The amount of streaming data between each metadata block
+ */
+ guint metaint;
+
+ /**
+ * The metadata as #Page which is currently being sent to the client.
+ */
+ Page *metadata;
+
+ /*
+ * The amount of bytes which were already sent from the metadata.
+ */
+ size_t metadata_current_position;
+
+ /**
+ * The amount of streaming data sent to the client
+ * since the last icy information was sent.
+ */
+ guint metadata_fill;
+
+public:
+ /**
+ * @param httpd the HTTP output device
+ * @param fd the socket file descriptor
+ */
+ HttpdClient(HttpdOutput *httpd, int _fd, EventLoop &_loop,
+ bool _metadata_supported);
+
+ /**
+ * Note: this does not remove the client from the
+ * #HttpdOutput object.
+ */
+ ~HttpdClient();
+
+ /**
+ * Frees the client and removes it from the server's client list.
+ */
+ void Close();
+
+ void LockClose();
+
+ /**
+ * Returns the total size of this client's page queue.
+ */
+ gcc_pure
+ size_t GetQueueSize() const;
+
+ /**
+ * Clears the page queue.
+ */
+ void CancelQueue();
+
+ /**
+ * Handle a line of the HTTP request.
+ */
+ bool HandleLine(const char *line);
+
+ /**
+ * Switch the client to the "RESPONSE" state.
+ */
+ void BeginResponse();
+
+ /**
+ * Sends the status line and response headers to the client.
+ */
+ bool SendResponse();
+
+ gcc_pure
+ ssize_t GetBytesTillMetaData() const;
+
+ ssize_t TryWritePage(const Page &page, size_t position);
+ ssize_t TryWritePageN(const Page &page, size_t position, ssize_t n);
+
+ bool TryWrite();
+
+ /**
+ * Appends a page to the client's queue.
+ */
+ void PushPage(Page *page);
+
+ /**
+ * Sends the passed metadata.
+ */
+ void PushMetaData(Page *page);
+
+protected:
+ virtual bool OnSocketReady(unsigned flags) override;
+ virtual InputResult OnSocketInput(const void *data,
+ size_t length) override;
+ virtual void OnSocketError(GError *error) override;
+ virtual void OnSocketClosed() override;
+};
+
+#endif
diff --git a/src/output/HttpdInternal.hxx b/src/output/HttpdInternal.hxx
new file mode 100644
index 000000000..e8a37e033
--- /dev/null
+++ b/src/output/HttpdInternal.hxx
@@ -0,0 +1,206 @@
+/*
+ * 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.
+ */
+
+/** \file
+ *
+ * Internal declarations for the "httpd" audio output plugin.
+ */
+
+#ifndef MPD_OUTPUT_HTTPD_INTERNAL_H
+#define MPD_OUTPUT_HTTPD_INTERNAL_H
+
+#include "OutputInternal.hxx"
+#include "Timer.hxx"
+#include "thread/Mutex.hxx"
+#include "event/ServerSocket.hxx"
+
+#include <forward_list>
+
+struct config_param;
+class EventLoop;
+class ServerSocket;
+class HttpdClient;
+class Page;
+struct Encoder;
+struct Tag;
+
+struct HttpdOutput final : private ServerSocket {
+ struct audio_output base;
+
+ /**
+ * True if the audio output is open and accepts client
+ * connections.
+ */
+ bool open;
+
+ /**
+ * The configured encoder plugin.
+ */
+ Encoder *encoder;
+
+ /**
+ * Number of bytes which were fed into the encoder, without
+ * ever receiving new output. This is used to estimate
+ * whether MPD should manually flush the encoder, to avoid
+ * buffer underruns in the client.
+ */
+ size_t unflushed_input;
+
+ /**
+ * The MIME type produced by the #encoder.
+ */
+ const char *content_type;
+
+ /**
+ * This mutex protects the listener socket and the client
+ * list.
+ */
+ mutable Mutex mutex;
+
+ /**
+ * A #Timer object to synchronize this output with the
+ * wallclock.
+ */
+ Timer *timer;
+
+ /**
+ * The header page, which is sent to every client on connect.
+ */
+ Page *header;
+
+ /**
+ * The metadata, which is sent to every client.
+ */
+ Page *metadata;
+
+ /**
+ * The configured name.
+ */
+ char const *name;
+ /**
+ * The configured genre.
+ */
+ char const *genre;
+ /**
+ * The configured website address.
+ */
+ char const *website;
+
+ /**
+ * A linked list containing all clients which are currently
+ * connected.
+ */
+ std::forward_list<HttpdClient> clients;
+
+ /**
+ * A temporary buffer for the httpd_output_read_page()
+ * function.
+ */
+ char buffer[32768];
+
+ /**
+ * The maximum and current number of clients connected
+ * at the same time.
+ */
+ guint clients_max, clients_cnt;
+
+ HttpdOutput(EventLoop &_loop);
+ ~HttpdOutput();
+
+ bool Configure(const config_param &param, GError **error_r);
+
+ bool Bind(GError **error_r);
+ void Unbind();
+
+ /**
+ * Caller must lock the mutex.
+ */
+ bool OpenEncoder(AudioFormat &audio_format,
+ GError **error_r);
+
+ /**
+ * Caller must lock the mutex.
+ */
+ bool Open(AudioFormat &audio_format, GError **error_r);
+
+ /**
+ * Caller must lock the mutex.
+ */
+ void Close();
+
+ /**
+ * Check whether there is at least one client.
+ *
+ * Caller must lock the mutex.
+ */
+ gcc_pure
+ bool HasClients() const {
+ return !clients.empty();
+ }
+
+ /**
+ * Check whether there is at least one client.
+ */
+ gcc_pure
+ bool LockHasClients() const {
+ const ScopeLock protect(mutex);
+ return HasClients();
+ }
+
+ void AddClient(int fd);
+
+ /**
+ * Removes a client from the httpd_output.clients linked list.
+ */
+ void RemoveClient(HttpdClient &client);
+
+ /**
+ * Sends the encoder header to the client. This is called
+ * right after the response headers have been sent.
+ */
+ void SendHeader(HttpdClient &client) const;
+
+ /**
+ * Reads data from the encoder (as much as available) and
+ * returns it as a new #page object.
+ */
+ Page *ReadPage();
+
+ /**
+ * Broadcasts a page struct to all clients.
+ *
+ * Mutext must not be locked.
+ */
+ void BroadcastPage(Page *page);
+
+ /**
+ * Broadcasts data from the encoder to all clients.
+ */
+ void BroadcastFromEncoder();
+
+ bool EncodeAndPlay(const void *chunk, size_t size, GError **error_r);
+
+ void SendTag(const Tag *tag);
+
+private:
+ virtual void OnAccept(int fd, const sockaddr &address,
+ size_t address_length, int uid) override;
+};
+
+#endif
diff --git a/src/output/HttpdOutputPlugin.cxx b/src/output/HttpdOutputPlugin.cxx
new file mode 100644
index 000000000..4169aabfa
--- /dev/null
+++ b/src/output/HttpdOutputPlugin.cxx
@@ -0,0 +1,568 @@
+/*
+ * 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 "HttpdOutputPlugin.hxx"
+#include "HttpdInternal.hxx"
+#include "HttpdClient.hxx"
+#include "OutputAPI.hxx"
+#include "EncoderPlugin.hxx"
+#include "EncoderList.hxx"
+#include "resolver.h"
+#include "Page.hxx"
+#include "IcyMetaDataServer.hxx"
+#include "fd_util.h"
+#include "Main.hxx"
+
+#include <assert.h>
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#ifdef HAVE_LIBWRAP
+#include <sys/socket.h> /* needed for AF_UNIX */
+#include <tcpd.h>
+#endif
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "httpd_output"
+
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+httpd_output_quark(void)
+{
+ return g_quark_from_static_string("httpd_output");
+}
+
+inline
+HttpdOutput::HttpdOutput(EventLoop &_loop)
+ :ServerSocket(_loop),
+ encoder(nullptr), unflushed_input(0),
+ metadata(nullptr)
+{
+}
+
+HttpdOutput::~HttpdOutput()
+{
+ if (metadata != nullptr)
+ metadata->Unref();
+
+ if (encoder != nullptr)
+ encoder_finish(encoder);
+
+}
+
+inline bool
+HttpdOutput::Bind(GError **error_r)
+{
+ open = false;
+
+ const ScopeLock protect(mutex);
+ return ServerSocket::Open(error_r);
+}
+
+inline void
+HttpdOutput::Unbind()
+{
+ assert(!open);
+
+ const ScopeLock protect(mutex);
+ ServerSocket::Close();
+}
+
+inline bool
+HttpdOutput::Configure(const config_param &param, GError **error_r)
+{
+ /* read configuration */
+ name = param.GetBlockValue("name", "Set name in config");
+ genre = param.GetBlockValue("genre", "Set genre in config");
+ website = param.GetBlockValue("website", "Set website in config");
+
+ guint port = param.GetBlockValue("port", 8000u);
+
+ const char *encoder_name =
+ param.GetBlockValue("encoder", "vorbis");
+ const auto encoder_plugin = encoder_plugin_get(encoder_name);
+ if (encoder_plugin == NULL) {
+ g_set_error(error_r, httpd_output_quark(), 0,
+ "No such encoder: %s", encoder_name);
+ return false;
+ }
+
+ clients_max = param.GetBlockValue("max_clients", 0u);
+
+ /* set up bind_to_address */
+
+ const char *bind_to_address = param.GetBlockValue("bind_to_address");
+ bool success = bind_to_address != NULL &&
+ strcmp(bind_to_address, "any") != 0
+ ? AddHost(bind_to_address, port, error_r)
+ : AddPort(port, error_r);
+ if (!success)
+ return false;
+
+ /* initialize encoder */
+
+ encoder = encoder_init(*encoder_plugin, param, error_r);
+ if (encoder == nullptr)
+ return false;
+
+ /* determine content type */
+ content_type = encoder_get_mime_type(encoder);
+ if (content_type == nullptr)
+ content_type = "application/octet-stream";
+
+ return true;
+}
+
+static struct audio_output *
+httpd_output_init(const struct config_param &param,
+ GError **error_r)
+{
+ HttpdOutput *httpd = new HttpdOutput(*main_loop);
+
+ if (!ao_base_init(&httpd->base, &httpd_output_plugin, param,
+ error_r)) {
+ delete httpd;
+ return nullptr;
+ }
+
+ if (!httpd->Configure(param, error_r)) {
+ 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));
+}
+
+#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);
+
+ ao_base_finish(&httpd->base);
+ delete httpd;
+}
+
+/**
+ * Creates a new #HttpdClient object and adds it into the
+ * HttpdOutput.clients linked list.
+ */
+inline void
+HttpdOutput::AddClient(int fd)
+{
+ clients.emplace_front(this, fd, GetEventLoop(),
+ encoder->plugin.tag == nullptr);
+ ++clients_cnt;
+
+ /* pass metadata to client */
+ if (metadata != nullptr)
+ clients.front().PushMetaData(metadata);
+}
+
+void
+HttpdOutput::OnAccept(int fd, const sockaddr &address,
+ size_t address_length, gcc_unused int uid)
+{
+ /* the listener socket has become readable - a client has
+ connected */
+
+#ifdef HAVE_LIBWRAP
+ if (address.sa_family != AF_UNIX) {
+ char *hostaddr = sockaddr_to_string(&address, address_length, NULL);
+ const char *progname = g_get_prgname();
+
+ struct request_info req;
+ request_init(&req, RQ_FILE, fd, RQ_DAEMON, progname, 0);
+
+ fromhost(&req);
+
+ if (!hosts_access(&req)) {
+ /* tcp wrappers says no */
+ g_warning("libwrap refused connection (libwrap=%s) from %s",
+ progname, hostaddr);
+ g_free(hostaddr);
+ close_socket(fd);
+ return;
+ }
+
+ g_free(hostaddr);
+ }
+#else
+ (void)address;
+ (void)address_length;
+#endif /* HAVE_WRAP */
+
+ const ScopeLock protect(mutex);
+
+ if (fd >= 0) {
+ /* can we allow additional client */
+ if (open && (clients_max == 0 || clients_cnt < clients_max))
+ AddClient(fd);
+ else
+ close_socket(fd);
+ } else if (fd < 0 && errno != EINTR) {
+ g_warning("accept() failed: %s", g_strerror(errno));
+ }
+}
+
+Page *
+HttpdOutput::ReadPage()
+{
+ if (unflushed_input >= 65536) {
+ /* we have fed a lot of input into the encoder, but it
+ didn't give anything back yet - flush now to avoid
+ buffer underruns */
+ encoder_flush(encoder, NULL);
+ unflushed_input = 0;
+ }
+
+ size_t size = 0;
+ do {
+ size_t nbytes = encoder_read(encoder,
+ buffer + size,
+ sizeof(buffer) - size);
+ if (nbytes == 0)
+ break;
+
+ unflushed_input = 0;
+
+ size += nbytes;
+ } while (size < sizeof(buffer));
+
+ if (size == 0)
+ return NULL;
+
+ return Page::Copy(buffer, size);
+}
+
+static bool
+httpd_output_enable(struct audio_output *ao, GError **error_r)
+{
+ HttpdOutput *httpd = Cast(ao);
+
+ return httpd->Bind(error_r);
+}
+
+static void
+httpd_output_disable(struct audio_output *ao)
+{
+ HttpdOutput *httpd = Cast(ao);
+
+ httpd->Unbind();
+}
+
+inline bool
+HttpdOutput::OpenEncoder(AudioFormat &audio_format, GError **error)
+{
+ if (!encoder_open(encoder, audio_format, error))
+ return false;
+
+ /* we have to remember the encoder header, i.e. the first
+ bytes of encoder output after opening it, because it has to
+ be sent to every new client */
+ header = ReadPage();
+
+ unflushed_input = 0;
+
+ return true;
+}
+
+inline bool
+HttpdOutput::Open(AudioFormat &audio_format, GError **error_r)
+{
+ assert(!open);
+ assert(clients.empty());
+
+ /* open the encoder */
+
+ if (!OpenEncoder(audio_format, error_r))
+ return false;
+
+ /* initialize other attributes */
+
+ clients_cnt = 0;
+ timer = new Timer(audio_format);
+
+ open = true;
+
+ return true;
+}
+
+static bool
+httpd_output_open(struct audio_output *ao, AudioFormat &audio_format,
+ GError **error)
+{
+ HttpdOutput *httpd = Cast(ao);
+
+ assert(httpd->clients.empty());
+
+ const ScopeLock protect(httpd->mutex);
+ return httpd->Open(audio_format, error);
+}
+
+inline void
+HttpdOutput::Close()
+{
+ assert(open);
+
+ open = false;
+
+ delete timer;
+
+ clients.clear();
+
+ if (header != NULL)
+ header->Unref();
+
+ encoder_close(encoder);
+}
+
+static void
+httpd_output_close(struct audio_output *ao)
+{
+ HttpdOutput *httpd = Cast(ao);
+
+ const ScopeLock protect(httpd->mutex);
+ httpd->Close();
+}
+
+void
+HttpdOutput::RemoveClient(HttpdClient &client)
+{
+ assert(clients_cnt > 0);
+
+ for (auto prev = clients.before_begin(), i = std::next(prev);;
+ prev = i, i = std::next(prev)) {
+ assert(i != clients.end());
+ if (&*i == &client) {
+ clients.erase_after(prev);
+ clients_cnt--;
+ break;
+ }
+ }
+}
+
+void
+HttpdOutput::SendHeader(HttpdClient &client) const
+{
+ if (header != NULL)
+ client.PushPage(header);
+}
+
+static unsigned
+httpd_output_delay(struct audio_output *ao)
+{
+ HttpdOutput *httpd = Cast(ao);
+
+ if (!httpd->LockHasClients() && httpd->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();
+
+ /* some arbitrary delay that is long enough to avoid
+ consuming too much CPU, and short enough to notice
+ new clients quickly enough */
+ return 1000;
+ }
+
+ return httpd->timer->IsStarted()
+ ? httpd->timer->GetDelay()
+ : 0;
+}
+
+void
+HttpdOutput::BroadcastPage(Page *page)
+{
+ assert(page != NULL);
+
+ const ScopeLock protect(mutex);
+ for (auto &client : clients)
+ client.PushPage(page);
+}
+
+void
+HttpdOutput::BroadcastFromEncoder()
+{
+ mutex.lock();
+ for (auto &client : clients) {
+ if (client.GetQueueSize() > 256 * 1024) {
+ g_debug("client is too slow, flushing its queue");
+ client.CancelQueue();
+ }
+ }
+ mutex.unlock();
+
+ Page *page;
+ while ((page = ReadPage()) != nullptr) {
+ BroadcastPage(page);
+ page->Unref();
+ }
+}
+
+inline bool
+HttpdOutput::EncodeAndPlay(const void *chunk, size_t size, GError **error_r)
+{
+ if (!encoder_write(encoder, chunk, size, error_r))
+ return false;
+
+ unflushed_input += size;
+
+ BroadcastFromEncoder();
+ return true;
+}
+
+static size_t
+httpd_output_play(struct audio_output *ao, const void *chunk, size_t size,
+ GError **error_r)
+{
+ HttpdOutput *httpd = Cast(ao);
+
+ if (httpd->LockHasClients()) {
+ if (!httpd->EncodeAndPlay(chunk, size, error_r))
+ return 0;
+ }
+
+ if (!httpd->timer->IsStarted())
+ httpd->timer->Start();
+ httpd->timer->Add(size);
+
+ return size;
+}
+
+static bool
+httpd_output_pause(struct audio_output *ao)
+{
+ HttpdOutput *httpd = Cast(ao);
+
+ if (httpd->LockHasClients()) {
+ static const char silence[1020] = { 0 };
+ return httpd_output_play(ao, silence, sizeof(silence),
+ NULL) > 0;
+ } else {
+ return true;
+ }
+}
+
+inline void
+HttpdOutput::SendTag(const Tag *tag)
+{
+ assert(tag != NULL);
+
+ if (encoder->plugin.tag != nullptr) {
+ /* embed encoder tags */
+
+ /* flush the current stream, and end it */
+
+ encoder_pre_tag(encoder, NULL);
+ BroadcastFromEncoder();
+
+ /* send the tag to the encoder - which starts a new
+ stream now */
+
+ encoder_tag(encoder, tag, NULL);
+
+ /* the first page generated by the encoder will now be
+ used as the new "header" page, which is sent to all
+ new clients */
+
+ Page *page = ReadPage();
+ if (page != NULL) {
+ if (header != NULL)
+ header->Unref();
+ header = page;
+ BroadcastPage(page);
+ }
+ } else {
+ /* use Icy-Metadata */
+
+ if (metadata != NULL)
+ metadata->Unref();
+
+ static constexpr tag_type types[] = {
+ TAG_ALBUM, TAG_ARTIST, TAG_TITLE,
+ TAG_NUM_OF_ITEM_TYPES
+ };
+
+ metadata = icy_server_metadata_page(*tag, &types[0]);
+ if (metadata != NULL) {
+ const ScopeLock protect(mutex);
+ for (auto &client : clients)
+ client.PushMetaData(metadata);
+ }
+ }
+}
+
+static void
+httpd_output_tag(struct audio_output *ao, const Tag *tag)
+{
+ HttpdOutput *httpd = Cast(ao);
+
+ httpd->SendTag(tag);
+}
+
+static void
+httpd_output_cancel(struct audio_output *ao)
+{
+ HttpdOutput *httpd = Cast(ao);
+
+ const ScopeLock protect(httpd->mutex);
+ for (auto &client : httpd->clients)
+ client.CancelQueue();
+}
+
+const struct audio_output_plugin httpd_output_plugin = {
+ "httpd",
+ nullptr,
+ httpd_output_init,
+ httpd_output_finish,
+ httpd_output_enable,
+ httpd_output_disable,
+ httpd_output_open,
+ httpd_output_close,
+ httpd_output_delay,
+ httpd_output_tag,
+ httpd_output_play,
+ nullptr,
+ httpd_output_cancel,
+ httpd_output_pause,
+ nullptr,
+};
diff --git a/src/output/HttpdOutputPlugin.hxx b/src/output/HttpdOutputPlugin.hxx
new file mode 100644
index 000000000..c74d2bd4a
--- /dev/null
+++ b/src/output/HttpdOutputPlugin.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_HTTPD_OUTPUT_PLUGIN_HXX
+#define MPD_HTTPD_OUTPUT_PLUGIN_HXX
+
+extern const struct audio_output_plugin httpd_output_plugin;
+
+#endif
diff --git a/src/output/JackOutputPlugin.cxx b/src/output/JackOutputPlugin.cxx
new file mode 100644
index 000000000..241857d82
--- /dev/null
+++ b/src/output/JackOutputPlugin.cxx
@@ -0,0 +1,776 @@
+/*
+ * 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 "JackOutputPlugin.hxx"
+#include "OutputAPI.hxx"
+
+#include <assert.h>
+
+#include <glib.h>
+#include <jack/jack.h>
+#include <jack/types.h>
+#include <jack/ringbuffer.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <errno.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "jack"
+
+enum {
+ MAX_PORTS = 16,
+};
+
+static const size_t jack_sample_size = sizeof(jack_default_audio_sample_t);
+
+struct JackOutput {
+ struct audio_output base;
+
+ /**
+ * libjack options passed to jack_client_open().
+ */
+ jack_options_t options;
+
+ const char *name;
+
+ const char *server_name;
+
+ /* configuration */
+
+ char *source_ports[MAX_PORTS];
+ unsigned num_source_ports;
+
+ char *destination_ports[MAX_PORTS];
+ unsigned num_destination_ports;
+
+ size_t ringbuffer_size;
+
+ /* the current audio format */
+ AudioFormat audio_format;
+
+ /* jack library stuff */
+ jack_port_t *ports[MAX_PORTS];
+ jack_client_t *client;
+ jack_ringbuffer_t *ringbuffer[MAX_PORTS];
+
+ bool shutdown;
+
+ /**
+ * While this flag is set, the "process" callback generates
+ * silence.
+ */
+ bool pause;
+
+ bool Initialize(const config_param &param, GError **error_r) {
+ return ao_base_init(&base, &jack_output_plugin, param,
+ error_r);
+ }
+
+ void Deinitialize() {
+ ao_base_finish(&base);
+ }
+};
+
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+jack_output_quark(void)
+{
+ return g_quark_from_static_string("jack_output");
+}
+
+/**
+ * Determine the number of frames guaranteed to be available on all
+ * channels.
+ */
+static jack_nframes_t
+mpd_jack_available(const JackOutput *jd)
+{
+ size_t min = jack_ringbuffer_read_space(jd->ringbuffer[0]);
+
+ for (unsigned i = 1; i < jd->audio_format.channels; ++i) {
+ size_t current = jack_ringbuffer_read_space(jd->ringbuffer[i]);
+ if (current < min)
+ min = current;
+ }
+
+ assert(min % jack_sample_size == 0);
+
+ return min / jack_sample_size;
+}
+
+static int
+mpd_jack_process(jack_nframes_t nframes, void *arg)
+{
+ JackOutput *jd = (JackOutput *) arg;
+
+ if (nframes <= 0)
+ return 0;
+
+ if (jd->pause) {
+ /* empty the ring buffers */
+
+ const jack_nframes_t available = mpd_jack_available(jd);
+ for (unsigned i = 0; i < jd->audio_format.channels; ++i)
+ jack_ringbuffer_read_advance(jd->ringbuffer[i],
+ available * jack_sample_size);
+
+ /* generate silence while MPD is paused */
+
+ for (unsigned i = 0; i < jd->audio_format.channels; ++i) {
+ jack_default_audio_sample_t *out =
+ (jack_default_audio_sample_t *)
+ jack_port_get_buffer(jd->ports[i], nframes);
+
+ for (jack_nframes_t f = 0; f < nframes; ++f)
+ out[f] = 0.0;
+ }
+
+ return 0;
+ }
+
+ jack_nframes_t available = mpd_jack_available(jd);
+ if (available > nframes)
+ available = nframes;
+
+ for (unsigned i = 0; i < jd->audio_format.channels; ++i) {
+ jack_default_audio_sample_t *out =
+ (jack_default_audio_sample_t *)
+ jack_port_get_buffer(jd->ports[i], nframes);
+ if (out == nullptr)
+ /* workaround for libjack1 bug: if the server
+ connection fails, the process callback is
+ invoked anyway, but unable to get a
+ buffer */
+ continue;
+
+ jack_ringbuffer_read(jd->ringbuffer[i],
+ (char *)out, available * jack_sample_size);
+
+ for (jack_nframes_t f = available; f < nframes; ++f)
+ /* ringbuffer underrun, fill with silence */
+ out[f] = 0.0;
+ }
+
+ /* generate silence for the unused source ports */
+
+ for (unsigned i = jd->audio_format.channels;
+ i < jd->num_source_ports; ++i) {
+ jack_default_audio_sample_t *out =
+ (jack_default_audio_sample_t *)
+ jack_port_get_buffer(jd->ports[i], nframes);
+ if (out == nullptr)
+ /* workaround for libjack1 bug: if the server
+ connection fails, the process callback is
+ invoked anyway, but unable to get a
+ buffer */
+ continue;
+
+ for (jack_nframes_t f = 0; f < nframes; ++f)
+ out[f] = 0.0;
+ }
+
+ return 0;
+}
+
+static void
+mpd_jack_shutdown(void *arg)
+{
+ JackOutput *jd = (JackOutput *) arg;
+ jd->shutdown = true;
+}
+
+static void
+set_audioformat(JackOutput *jd, AudioFormat &audio_format)
+{
+ audio_format.sample_rate = jack_get_sample_rate(jd->client);
+
+ if (jd->num_source_ports == 1)
+ audio_format.channels = 1;
+ else if (audio_format.channels > jd->num_source_ports)
+ audio_format.channels = 2;
+
+ if (audio_format.format != SampleFormat::S16 &&
+ audio_format.format != SampleFormat::S24_P32)
+ audio_format.format = SampleFormat::S24_P32;
+}
+
+static void
+mpd_jack_error(const char *msg)
+{
+ g_warning("%s", msg);
+}
+
+#ifdef HAVE_JACK_SET_INFO_FUNCTION
+static void
+mpd_jack_info(const char *msg)
+{
+ g_message("%s", msg);
+}
+#endif
+
+/**
+ * Disconnect the JACK client.
+ */
+static void
+mpd_jack_disconnect(JackOutput *jd)
+{
+ assert(jd != nullptr);
+ assert(jd->client != nullptr);
+
+ jack_deactivate(jd->client);
+ jack_client_close(jd->client);
+ jd->client = nullptr;
+}
+
+/**
+ * Connect the JACK client and performs some basic setup
+ * (e.g. register callbacks).
+ */
+static bool
+mpd_jack_connect(JackOutput *jd, GError **error_r)
+{
+ jack_status_t status;
+
+ assert(jd != nullptr);
+
+ jd->shutdown = false;
+
+ jd->client = jack_client_open(jd->name, jd->options, &status,
+ jd->server_name);
+ if (jd->client == nullptr) {
+ g_set_error(error_r, jack_output_quark(), 0,
+ "Failed to connect to JACK server, status=%d",
+ status);
+ return false;
+ }
+
+ jack_set_process_callback(jd->client, mpd_jack_process, jd);
+ jack_on_shutdown(jd->client, mpd_jack_shutdown, jd);
+
+ for (unsigned i = 0; i < jd->num_source_ports; ++i) {
+ jd->ports[i] = jack_port_register(jd->client,
+ jd->source_ports[i],
+ JACK_DEFAULT_AUDIO_TYPE,
+ JackPortIsOutput, 0);
+ if (jd->ports[i] == nullptr) {
+ g_set_error(error_r, jack_output_quark(), 0,
+ "Cannot register output port \"%s\"",
+ jd->source_ports[i]);
+ mpd_jack_disconnect(jd);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool
+mpd_jack_test_default_device(void)
+{
+ return true;
+}
+
+static unsigned
+parse_port_list(int line, const char *source, char **dest, GError **error_r)
+{
+ char **list = g_strsplit(source, ",", 0);
+ unsigned n = 0;
+
+ for (n = 0; list[n] != nullptr; ++n) {
+ if (n >= MAX_PORTS) {
+ g_set_error(error_r, jack_output_quark(), 0,
+ "too many port names in line %d",
+ line);
+ return 0;
+ }
+
+ dest[n] = list[n];
+ }
+
+ g_free(list);
+
+ if (n == 0) {
+ g_set_error(error_r, jack_output_quark(), 0,
+ "at least one port name expected in line %d",
+ line);
+ return 0;
+ }
+
+ return n;
+}
+
+static struct audio_output *
+mpd_jack_init(const config_param &param, GError **error_r)
+{
+ JackOutput *jd = new JackOutput();
+
+ if (!jd->Initialize(param, error_r)) {
+ delete jd;
+ return nullptr;
+ }
+
+ const char *value;
+
+ jd->options = JackNullOption;
+
+ jd->name = param.GetBlockValue("client_name", nullptr);
+ if (jd->name != nullptr)
+ jd->options = jack_options_t(jd->options | JackUseExactName);
+ else
+ /* if there's a no configured client name, we don't
+ care about the JackUseExactName option */
+ jd->name = "Music Player Daemon";
+
+ jd->server_name = param.GetBlockValue("server_name", nullptr);
+ if (jd->server_name != nullptr)
+ jd->options = jack_options_t(jd->options | JackServerName);
+
+ if (!param.GetBlockValue("autostart", false))
+ jd->options = jack_options_t(jd->options | JackNoStartServer);
+
+ /* configure the source ports */
+
+ value = param.GetBlockValue("source_ports", "left,right");
+ jd->num_source_ports = parse_port_list(param.line, value,
+ jd->source_ports, error_r);
+ if (jd->num_source_ports == 0)
+ return nullptr;
+
+ /* configure the destination ports */
+
+ value = param.GetBlockValue("destination_ports", nullptr);
+ if (value == nullptr) {
+ /* compatibility with MPD < 0.16 */
+ value = param.GetBlockValue("ports", nullptr);
+ if (value != nullptr)
+ g_warning("deprecated option 'ports' in line %d",
+ param.line);
+ }
+
+ if (value != nullptr) {
+ jd->num_destination_ports =
+ parse_port_list(param.line, value,
+ jd->destination_ports, error_r);
+ if (jd->num_destination_ports == 0)
+ return nullptr;
+ } else {
+ jd->num_destination_ports = 0;
+ }
+
+ if (jd->num_destination_ports > 0 &&
+ jd->num_destination_ports != jd->num_source_ports)
+ g_warning("number of source ports (%u) mismatches the "
+ "number of destination ports (%u) in line %d",
+ jd->num_source_ports, jd->num_destination_ports,
+ param.line);
+
+ jd->ringbuffer_size = param.GetBlockValue("ringbuffer_size", 32768u);
+
+ jack_set_error_function(mpd_jack_error);
+
+#ifdef HAVE_JACK_SET_INFO_FUNCTION
+ jack_set_info_function(mpd_jack_info);
+#endif
+
+ return &jd->base;
+}
+
+static void
+mpd_jack_finish(struct audio_output *ao)
+{
+ JackOutput *jd = (JackOutput *)ao;
+
+ for (unsigned i = 0; i < jd->num_source_ports; ++i)
+ g_free(jd->source_ports[i]);
+
+ for (unsigned i = 0; i < jd->num_destination_ports; ++i)
+ g_free(jd->destination_ports[i]);
+
+ jd->Deinitialize();
+ delete jd;
+}
+
+static bool
+mpd_jack_enable(struct audio_output *ao, GError **error_r)
+{
+ JackOutput *jd = (JackOutput *)ao;
+
+ for (unsigned i = 0; i < jd->num_source_ports; ++i)
+ jd->ringbuffer[i] = nullptr;
+
+ return mpd_jack_connect(jd, error_r);
+}
+
+static void
+mpd_jack_disable(struct audio_output *ao)
+{
+ JackOutput *jd = (JackOutput *)ao;
+
+ if (jd->client != nullptr)
+ mpd_jack_disconnect(jd);
+
+ for (unsigned i = 0; i < jd->num_source_ports; ++i) {
+ if (jd->ringbuffer[i] != nullptr) {
+ jack_ringbuffer_free(jd->ringbuffer[i]);
+ jd->ringbuffer[i] = nullptr;
+ }
+ }
+}
+
+/**
+ * Stops the playback on the JACK connection.
+ */
+static void
+mpd_jack_stop(JackOutput *jd)
+{
+ assert(jd != nullptr);
+
+ if (jd->client == nullptr)
+ return;
+
+ if (jd->shutdown)
+ /* the connection has failed; close it */
+ mpd_jack_disconnect(jd);
+ else
+ /* the connection is alive: just stop playback */
+ jack_deactivate(jd->client);
+}
+
+static bool
+mpd_jack_start(JackOutput *jd, GError **error_r)
+{
+ const char *destination_ports[MAX_PORTS], **jports;
+ const char *duplicate_port = nullptr;
+ unsigned num_destination_ports;
+
+ assert(jd->client != nullptr);
+ assert(jd->audio_format.channels <= jd->num_source_ports);
+
+ /* allocate the ring buffers on the first open(); these
+ persist until MPD exits. It's too unsafe to delete them
+ because we can never know when mpd_jack_process() gets
+ called */
+ for (unsigned i = 0; i < jd->num_source_ports; ++i) {
+ if (jd->ringbuffer[i] == nullptr)
+ jd->ringbuffer[i] =
+ jack_ringbuffer_create(jd->ringbuffer_size);
+
+ /* clear the ring buffer to be sure that data from
+ previous playbacks are gone */
+ jack_ringbuffer_reset(jd->ringbuffer[i]);
+ }
+
+ if ( jack_activate(jd->client) ) {
+ g_set_error(error_r, jack_output_quark(), 0,
+ "cannot activate client");
+ mpd_jack_stop(jd);
+ return false;
+ }
+
+ if (jd->num_destination_ports == 0) {
+ /* no output ports were configured - ask libjack for
+ defaults */
+ jports = jack_get_ports(jd->client, nullptr, nullptr,
+ JackPortIsPhysical | JackPortIsInput);
+ if (jports == nullptr) {
+ g_set_error(error_r, jack_output_quark(), 0,
+ "no ports found");
+ mpd_jack_stop(jd);
+ return false;
+ }
+
+ assert(*jports != nullptr);
+
+ for (num_destination_ports = 0;
+ num_destination_ports < MAX_PORTS &&
+ jports[num_destination_ports] != nullptr;
+ ++num_destination_ports) {
+ g_debug("destination_port[%u] = '%s'\n",
+ num_destination_ports,
+ jports[num_destination_ports]);
+ destination_ports[num_destination_ports] =
+ jports[num_destination_ports];
+ }
+ } else {
+ /* use the configured output ports */
+
+ num_destination_ports = jd->num_destination_ports;
+ memcpy(destination_ports, jd->destination_ports,
+ num_destination_ports * sizeof(*destination_ports));
+
+ jports = nullptr;
+ }
+
+ assert(num_destination_ports > 0);
+
+ if (jd->audio_format.channels >= 2 && num_destination_ports == 1) {
+ /* mix stereo signal on one speaker */
+
+ while (num_destination_ports < jd->audio_format.channels)
+ destination_ports[num_destination_ports++] =
+ destination_ports[0];
+ } else if (num_destination_ports > jd->audio_format.channels) {
+ if (jd->audio_format.channels == 1 && num_destination_ports > 2) {
+ /* mono input file: connect the one source
+ channel to the both destination channels */
+ duplicate_port = destination_ports[1];
+ num_destination_ports = 1;
+ } else
+ /* connect only as many ports as we need */
+ num_destination_ports = jd->audio_format.channels;
+ }
+
+ assert(num_destination_ports <= jd->num_source_ports);
+
+ for (unsigned i = 0; i < num_destination_ports; ++i) {
+ int ret;
+
+ ret = jack_connect(jd->client, jack_port_name(jd->ports[i]),
+ destination_ports[i]);
+ if (ret != 0) {
+ g_set_error(error_r, jack_output_quark(), 0,
+ "Not a valid JACK port: %s",
+ destination_ports[i]);
+
+ if (jports != nullptr)
+ free(jports);
+
+ mpd_jack_stop(jd);
+ return false;
+ }
+ }
+
+ if (duplicate_port != nullptr) {
+ /* mono input file: connect the one source channel to
+ the both destination channels */
+ int ret;
+
+ ret = jack_connect(jd->client, jack_port_name(jd->ports[0]),
+ duplicate_port);
+ if (ret != 0) {
+ g_set_error(error_r, jack_output_quark(), 0,
+ "Not a valid JACK port: %s",
+ duplicate_port);
+
+ if (jports != nullptr)
+ free(jports);
+
+ mpd_jack_stop(jd);
+ return false;
+ }
+ }
+
+ if (jports != nullptr)
+ free(jports);
+
+ return true;
+}
+
+static bool
+mpd_jack_open(struct audio_output *ao, AudioFormat &audio_format,
+ GError **error_r)
+{
+ JackOutput *jd = (JackOutput *)ao;
+
+ assert(jd != nullptr);
+
+ jd->pause = false;
+
+ if (jd->client != nullptr && jd->shutdown)
+ mpd_jack_disconnect(jd);
+
+ if (jd->client == nullptr && !mpd_jack_connect(jd, error_r))
+ return false;
+
+ set_audioformat(jd, audio_format);
+ jd->audio_format = audio_format;
+
+ if (!mpd_jack_start(jd, error_r))
+ return false;
+
+ return true;
+}
+
+static void
+mpd_jack_close(G_GNUC_UNUSED struct audio_output *ao)
+{
+ JackOutput *jd = (JackOutput *)ao;
+
+ mpd_jack_stop(jd);
+}
+
+static unsigned
+mpd_jack_delay(struct audio_output *ao)
+{
+ JackOutput *jd = (JackOutput *)ao;
+
+ return jd->base.pause && jd->pause && !jd->shutdown
+ ? 1000
+ : 0;
+}
+
+static inline jack_default_audio_sample_t
+sample_16_to_jack(int16_t sample)
+{
+ return sample / (jack_default_audio_sample_t)(1 << (16 - 1));
+}
+
+static void
+mpd_jack_write_samples_16(JackOutput *jd, const int16_t *src,
+ unsigned num_samples)
+{
+ jack_default_audio_sample_t sample;
+ unsigned i;
+
+ while (num_samples-- > 0) {
+ for (i = 0; i < jd->audio_format.channels; ++i) {
+ sample = sample_16_to_jack(*src++);
+ jack_ringbuffer_write(jd->ringbuffer[i],
+ (const char *)&sample,
+ sizeof(sample));
+ }
+ }
+}
+
+static inline jack_default_audio_sample_t
+sample_24_to_jack(int32_t sample)
+{
+ return sample / (jack_default_audio_sample_t)(1 << (24 - 1));
+}
+
+static void
+mpd_jack_write_samples_24(JackOutput *jd, const int32_t *src,
+ unsigned num_samples)
+{
+ jack_default_audio_sample_t sample;
+ unsigned i;
+
+ while (num_samples-- > 0) {
+ for (i = 0; i < jd->audio_format.channels; ++i) {
+ sample = sample_24_to_jack(*src++);
+ jack_ringbuffer_write(jd->ringbuffer[i],
+ (const char *)&sample,
+ sizeof(sample));
+ }
+ }
+}
+
+static void
+mpd_jack_write_samples(JackOutput *jd, const void *src,
+ unsigned num_samples)
+{
+ switch (jd->audio_format.format) {
+ case SampleFormat::S16:
+ mpd_jack_write_samples_16(jd, (const int16_t*)src,
+ num_samples);
+ break;
+
+ case SampleFormat::S24_P32:
+ mpd_jack_write_samples_24(jd, (const int32_t*)src,
+ num_samples);
+ break;
+
+ default:
+ assert(false);
+ gcc_unreachable();
+ }
+}
+
+static size_t
+mpd_jack_play(struct audio_output *ao, const void *chunk, size_t size,
+ GError **error_r)
+{
+ JackOutput *jd = (JackOutput *)ao;
+ const size_t frame_size = jd->audio_format.GetFrameSize();
+ size_t space = 0, space1;
+
+ jd->pause = false;
+
+ assert(size % frame_size == 0);
+ size /= frame_size;
+
+ while (true) {
+ if (jd->shutdown) {
+ g_set_error(error_r, jack_output_quark(), 0,
+ "Refusing to play, because "
+ "there is no client thread");
+ return 0;
+ }
+
+ space = jack_ringbuffer_write_space(jd->ringbuffer[0]);
+ for (unsigned i = 1; i < jd->audio_format.channels; ++i) {
+ space1 = jack_ringbuffer_write_space(jd->ringbuffer[i]);
+ if (space > space1)
+ /* send data symmetrically */
+ space = space1;
+ }
+
+ if (space >= jack_sample_size)
+ break;
+
+ /* XXX do something more intelligent to
+ synchronize */
+ g_usleep(1000);
+ }
+
+ space /= jack_sample_size;
+ if (space < size)
+ size = space;
+
+ mpd_jack_write_samples(jd, chunk, size);
+ return size * frame_size;
+}
+
+static bool
+mpd_jack_pause(struct audio_output *ao)
+{
+ JackOutput *jd = (JackOutput *)ao;
+
+ if (jd->shutdown)
+ return false;
+
+ jd->pause = true;
+
+ return true;
+}
+
+const struct audio_output_plugin jack_output_plugin = {
+ "jack",
+ mpd_jack_test_default_device,
+ mpd_jack_init,
+ mpd_jack_finish,
+ mpd_jack_enable,
+ mpd_jack_disable,
+ mpd_jack_open,
+ mpd_jack_close,
+ mpd_jack_delay,
+ nullptr,
+ mpd_jack_play,
+ nullptr,
+ nullptr,
+ mpd_jack_pause,
+ nullptr,
+};
diff --git a/src/output/JackOutputPlugin.hxx b/src/output/JackOutputPlugin.hxx
new file mode 100644
index 000000000..908105ad2
--- /dev/null
+++ b/src/output/JackOutputPlugin.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_JACK_OUTPUT_PLUGIN_HXX
+#define MPD_JACK_OUTPUT_PLUGIN_HXX
+
+extern const struct audio_output_plugin jack_output_plugin;
+
+#endif
diff --git a/src/output/NullOutputPlugin.cxx b/src/output/NullOutputPlugin.cxx
new file mode 100644
index 000000000..0ce32fbda
--- /dev/null
+++ b/src/output/NullOutputPlugin.cxx
@@ -0,0 +1,143 @@
+/*
+ * 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 "NullOutputPlugin.hxx"
+#include "OutputAPI.hxx"
+#include "Timer.hxx"
+
+#include <assert.h>
+
+struct NullOutput {
+ struct audio_output base;
+
+ bool sync;
+
+ Timer *timer;
+
+ bool Initialize(const config_param &param, GError **error_r) {
+ return ao_base_init(&base, &null_output_plugin, param,
+ error_r);
+ }
+
+ void Deinitialize() {
+ ao_base_finish(&base);
+ }
+};
+
+static struct audio_output *
+null_init(const config_param &param, GError **error_r)
+{
+ NullOutput *nd = new NullOutput();
+
+ if (!nd->Initialize(param, error_r)) {
+ delete nd;
+ return nullptr;
+ }
+
+ nd->sync = param.GetBlockValue("sync", true);
+
+ return &nd->base;
+}
+
+static void
+null_finish(struct audio_output *ao)
+{
+ NullOutput *nd = (NullOutput *)ao;
+
+ nd->Deinitialize();
+ delete nd;
+}
+
+static bool
+null_open(struct audio_output *ao, AudioFormat &audio_format,
+ gcc_unused GError **error)
+{
+ NullOutput *nd = (NullOutput *)ao;
+
+ if (nd->sync)
+ nd->timer = new Timer(audio_format);
+
+ return true;
+}
+
+static void
+null_close(struct audio_output *ao)
+{
+ NullOutput *nd = (NullOutput *)ao;
+
+ if (nd->sync)
+ delete nd->timer;
+}
+
+static unsigned
+null_delay(struct audio_output *ao)
+{
+ NullOutput *nd = (NullOutput *)ao;
+
+ return nd->sync && nd->timer->IsStarted()
+ ? nd->timer->GetDelay()
+ : 0;
+}
+
+static size_t
+null_play(struct audio_output *ao, gcc_unused const void *chunk, size_t size,
+ gcc_unused GError **error)
+{
+ NullOutput *nd = (NullOutput *)ao;
+ Timer *timer = nd->timer;
+
+ if (!nd->sync)
+ return size;
+
+ if (!timer->IsStarted())
+ timer->Start();
+ timer->Add(size);
+
+ return size;
+}
+
+static void
+null_cancel(struct audio_output *ao)
+{
+ NullOutput *nd = (NullOutput *)ao;
+
+ if (!nd->sync)
+ return;
+
+ nd->timer->Reset();
+}
+
+const struct audio_output_plugin null_output_plugin = {
+ "null",
+ nullptr,
+ null_init,
+ null_finish,
+ nullptr,
+ nullptr,
+ null_open,
+ null_close,
+ null_delay,
+ nullptr,
+ null_play,
+ nullptr,
+ null_cancel,
+ nullptr,
+ nullptr,
+};
diff --git a/src/output/NullOutputPlugin.hxx b/src/output/NullOutputPlugin.hxx
new file mode 100644
index 000000000..a58f1cb13
--- /dev/null
+++ b/src/output/NullOutputPlugin.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_NULL_OUTPUT_PLUGIN_HXX
+#define MPD_NULL_OUTPUT_PLUGIN_HXX
+
+extern const struct audio_output_plugin null_output_plugin;
+
+#endif
diff --git a/src/output/OSXOutputPlugin.cxx b/src/output/OSXOutputPlugin.cxx
new file mode 100644
index 000000000..6e42b2518
--- /dev/null
+++ b/src/output/OSXOutputPlugin.cxx
@@ -0,0 +1,436 @@
+/*
+ * 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 "OSXOutputPlugin.hxx"
+#include "OutputAPI.hxx"
+#include "util/fifo_buffer.h"
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+
+#include <glib.h>
+#include <CoreAudio/AudioHardware.h>
+#include <AudioUnit/AudioUnit.h>
+#include <CoreServices/CoreServices.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "osx"
+
+struct OSXOutput {
+ struct audio_output base;
+
+ /* configuration settings */
+ OSType component_subtype;
+ /* only applicable with kAudioUnitSubType_HALOutput */
+ const char *device_name;
+
+ AudioUnit au;
+ Mutex mutex;
+ Cond condition;
+
+ struct fifo_buffer *buffer;
+};
+
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+osx_output_quark(void)
+{
+ return g_quark_from_static_string("osx_output");
+}
+
+static bool
+osx_output_test_default_device(void)
+{
+ /* on a Mac, this is always the default plugin, if nothing
+ else is configured */
+ return true;
+}
+
+static void
+osx_output_configure(OSXOutput *oo, const config_param &param)
+{
+ const char *device = param.GetBlockValue("device");
+
+ if (device == NULL || 0 == strcmp(device, "default")) {
+ oo->component_subtype = kAudioUnitSubType_DefaultOutput;
+ oo->device_name = NULL;
+ }
+ else if (0 == strcmp(device, "system")) {
+ oo->component_subtype = kAudioUnitSubType_SystemOutput;
+ oo->device_name = NULL;
+ }
+ else {
+ oo->component_subtype = kAudioUnitSubType_HALOutput;
+ /* XXX am I supposed to g_strdup() this? */
+ oo->device_name = device;
+ }
+}
+
+static struct audio_output *
+osx_output_init(const config_param &param, GError **error_r)
+{
+ OSXOutput *oo = new OSXOutput();
+ if (!ao_base_init(&oo->base, &osx_output_plugin, param, error_r)) {
+ delete oo;
+ return NULL;
+ }
+
+ osx_output_configure(oo, param);
+
+ return &oo->base;
+}
+
+static void
+osx_output_finish(struct audio_output *ao)
+{
+ OSXOutput *oo = (OSXOutput *)ao;
+
+ delete oo;
+}
+
+static bool
+osx_output_set_device(OSXOutput *oo, GError **error)
+{
+ bool ret = true;
+ OSStatus status;
+ UInt32 size, numdevices;
+ AudioDeviceID *deviceids = NULL;
+ char name[256];
+ unsigned int i;
+
+ if (oo->component_subtype != kAudioUnitSubType_HALOutput)
+ goto done;
+
+ /* how many audio devices are there? */
+ status = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices,
+ &size,
+ NULL);
+ if (status != noErr) {
+ g_set_error(error, osx_output_quark(), status,
+ "Unable to determine number of OS X audio devices: %s",
+ GetMacOSStatusCommentString(status));
+ ret = false;
+ goto done;
+ }
+
+ /* what are the available audio device IDs? */
+ numdevices = size / sizeof(AudioDeviceID);
+ deviceids = new AudioDeviceID[numdevices];
+ status = AudioHardwareGetProperty(kAudioHardwarePropertyDevices,
+ &size,
+ deviceids);
+ if (status != noErr) {
+ g_set_error(error, osx_output_quark(), status,
+ "Unable to determine OS X audio device IDs: %s",
+ GetMacOSStatusCommentString(status));
+ ret = false;
+ goto done;
+ }
+
+ /* which audio device matches oo->device_name? */
+ for (i = 0; i < numdevices; i++) {
+ size = sizeof(name);
+ status = AudioDeviceGetProperty(deviceids[i], 0, false,
+ kAudioDevicePropertyDeviceName,
+ &size, name);
+ if (status != noErr) {
+ g_set_error(error, osx_output_quark(), status,
+ "Unable to determine OS X device name "
+ "(device %u): %s",
+ (unsigned int) deviceids[i],
+ GetMacOSStatusCommentString(status));
+ ret = false;
+ goto done;
+ }
+ if (strcmp(oo->device_name, name) == 0) {
+ g_debug("found matching device: ID=%u, name=%s",
+ (unsigned int) deviceids[i], name);
+ break;
+ }
+ }
+ if (i == numdevices) {
+ g_warning("Found no audio device with name '%s' "
+ "(will use default audio device)",
+ oo->device_name);
+ goto done;
+ }
+
+ status = AudioUnitSetProperty(oo->au,
+ kAudioOutputUnitProperty_CurrentDevice,
+ kAudioUnitScope_Global,
+ 0,
+ &(deviceids[i]),
+ sizeof(AudioDeviceID));
+ if (status != noErr) {
+ g_set_error(error, osx_output_quark(), status,
+ "Unable to set OS X audio output device: %s",
+ GetMacOSStatusCommentString(status));
+ ret = false;
+ goto done;
+ }
+ g_debug("set OS X audio output device ID=%u, name=%s",
+ (unsigned int) deviceids[i], name);
+
+done:
+ delete[] deviceids;
+ return ret;
+}
+
+static OSStatus
+osx_render(void *vdata,
+ G_GNUC_UNUSED AudioUnitRenderActionFlags *io_action_flags,
+ G_GNUC_UNUSED const AudioTimeStamp *in_timestamp,
+ G_GNUC_UNUSED UInt32 in_bus_number,
+ G_GNUC_UNUSED UInt32 in_number_frames,
+ AudioBufferList *buffer_list)
+{
+ OSXOutput *od = (OSXOutput *) vdata;
+ AudioBuffer *buffer = &buffer_list->mBuffers[0];
+ size_t buffer_size = buffer->mDataByteSize;
+
+ assert(od->buffer != NULL);
+
+ od->mutex.lock();
+
+ size_t nbytes;
+ const void *src = fifo_buffer_read(od->buffer, &nbytes);
+
+ if (src != NULL) {
+ if (nbytes > buffer_size)
+ nbytes = buffer_size;
+
+ memcpy(buffer->mData, src, nbytes);
+ fifo_buffer_consume(od->buffer, nbytes);
+ } else
+ nbytes = 0;
+
+ od->condition.signal();
+ od->mutex.unlock();
+
+ buffer->mDataByteSize = nbytes;
+
+ unsigned i;
+ for (i = 1; i < buffer_list->mNumberBuffers; ++i) {
+ buffer = &buffer_list->mBuffers[i];
+ buffer->mDataByteSize = 0;
+ }
+
+ return 0;
+}
+
+static bool
+osx_output_enable(struct audio_output *ao, GError **error_r)
+{
+ OSXOutput *oo = (OSXOutput *)ao;
+
+ ComponentDescription desc;
+ desc.componentType = kAudioUnitType_Output;
+ desc.componentSubType = oo->component_subtype;
+ desc.componentManufacturer = kAudioUnitManufacturer_Apple;
+ desc.componentFlags = 0;
+ desc.componentFlagsMask = 0;
+
+ Component comp = FindNextComponent(NULL, &desc);
+ if (comp == 0) {
+ g_set_error(error_r, osx_output_quark(), 0,
+ "Error finding OS X component");
+ return false;
+ }
+
+ OSStatus status = OpenAComponent(comp, &oo->au);
+ if (status != noErr) {
+ g_set_error(error_r, osx_output_quark(), status,
+ "Unable to open OS X component: %s",
+ GetMacOSStatusCommentString(status));
+ return false;
+ }
+
+ if (!osx_output_set_device(oo, error_r)) {
+ CloseComponent(oo->au);
+ return false;
+ }
+
+ AURenderCallbackStruct callback;
+ callback.inputProc = osx_render;
+ callback.inputProcRefCon = oo;
+
+ ComponentResult result =
+ AudioUnitSetProperty(oo->au,
+ kAudioUnitProperty_SetRenderCallback,
+ kAudioUnitScope_Input, 0,
+ &callback, sizeof(callback));
+ if (result != noErr) {
+ CloseComponent(oo->au);
+ g_set_error(error_r, osx_output_quark(), result,
+ "unable to set callback for OS X audio unit");
+ return false;
+ }
+
+ return true;
+}
+
+static void
+osx_output_disable(struct audio_output *ao)
+{
+ OSXOutput *oo = (OSXOutput *)ao;
+
+ CloseComponent(oo->au);
+}
+
+static void
+osx_output_cancel(struct audio_output *ao)
+{
+ OSXOutput *od = (OSXOutput *)ao;
+
+ const ScopeLock protect(od->mutex);
+ fifo_buffer_clear(od->buffer);
+}
+
+static void
+osx_output_close(struct audio_output *ao)
+{
+ OSXOutput *od = (OSXOutput *)ao;
+
+ AudioOutputUnitStop(od->au);
+ AudioUnitUninitialize(od->au);
+
+ fifo_buffer_free(od->buffer);
+}
+
+static bool
+osx_output_open(struct audio_output *ao, AudioFormat &audio_format, GError **error)
+{
+ OSXOutput *od = (OSXOutput *)ao;
+
+ AudioStreamBasicDescription stream_description;
+ stream_description.mSampleRate = audio_format.sample_rate;
+ stream_description.mFormatID = kAudioFormatLinearPCM;
+ stream_description.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
+
+ switch (audio_format.format) {
+ case SampleFormat::S8:
+ stream_description.mBitsPerChannel = 8;
+ break;
+
+ case SampleFormat::S16:
+ stream_description.mBitsPerChannel = 16;
+ break;
+
+ case SampleFormat::S32:
+ stream_description.mBitsPerChannel = 32;
+ break;
+
+ default:
+ audio_format.format = SampleFormat::S32;
+ stream_description.mBitsPerChannel = 32;
+ break;
+ }
+
+#if G_BYTE_ORDER == G_BIG_ENDIAN
+ stream_description.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
+#endif
+
+ stream_description.mBytesPerPacket = audio_format.GetFrameSize();
+ stream_description.mFramesPerPacket = 1;
+ stream_description.mBytesPerFrame = stream_description.mBytesPerPacket;
+ stream_description.mChannelsPerFrame = audio_format.channels;
+
+ ComponentResult result =
+ AudioUnitSetProperty(od->au, kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Input, 0,
+ &stream_description,
+ sizeof(stream_description));
+ if (result != noErr) {
+ g_set_error(error, osx_output_quark(), result,
+ "Unable to set format on OS X device");
+ return false;
+ }
+
+ OSStatus status = AudioUnitInitialize(od->au);
+ if (status != noErr) {
+ g_set_error(error, osx_output_quark(), status,
+ "Unable to initialize OS X audio unit: %s",
+ GetMacOSStatusCommentString(status));
+ return false;
+ }
+
+ /* create a buffer of 1s */
+ od->buffer = fifo_buffer_new(audio_format.sample_rate *
+ audio_format.GetFrameSize());
+
+ status = AudioOutputUnitStart(od->au);
+ if (status != 0) {
+ AudioUnitUninitialize(od->au);
+ g_set_error(error, osx_output_quark(), status,
+ "unable to start audio output: %s",
+ GetMacOSStatusCommentString(status));
+ return false;
+ }
+
+ return true;
+}
+
+static size_t
+osx_output_play(struct audio_output *ao, const void *chunk, size_t size,
+ G_GNUC_UNUSED GError **error)
+{
+ OSXOutput *od = (OSXOutput *)ao;
+
+ const ScopeLock protect(od->mutex);
+
+ void *dest;
+ size_t max_length;
+
+ while (true) {
+ dest = fifo_buffer_write(od->buffer, &max_length);
+ if (dest != NULL)
+ break;
+
+ /* wait for some free space in the buffer */
+ od->condition.wait(od->mutex);
+ }
+
+ if (size > max_length)
+ size = max_length;
+
+ memcpy(dest, chunk, size);
+ fifo_buffer_append(od->buffer, size);
+
+ return size;
+}
+
+const struct audio_output_plugin osx_output_plugin = {
+ "osx",
+ osx_output_test_default_device,
+ osx_output_init,
+ osx_output_finish,
+ osx_output_enable,
+ osx_output_disable,
+ osx_output_open,
+ osx_output_close,
+ nullptr,
+ nullptr,
+ osx_output_play,
+ nullptr,
+ osx_output_cancel,
+ nullptr,
+ nullptr,
+};
diff --git a/src/output/OSXOutputPlugin.hxx b/src/output/OSXOutputPlugin.hxx
new file mode 100644
index 000000000..2a4172880
--- /dev/null
+++ b/src/output/OSXOutputPlugin.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_OSX_OUTPUT_PLUGIN_HXX
+#define MPD_OSX_OUTPUT_PLUGIN_HXX
+
+extern const struct audio_output_plugin osx_output_plugin;
+
+#endif
diff --git a/src/output/OpenALOutputPlugin.cxx b/src/output/OpenALOutputPlugin.cxx
new file mode 100644
index 000000000..1864052fa
--- /dev/null
+++ b/src/output/OpenALOutputPlugin.cxx
@@ -0,0 +1,293 @@
+/*
+ * 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 "OpenALOutputPlugin.hxx"
+#include "OutputAPI.hxx"
+
+#include <glib.h>
+
+#ifndef HAVE_OSX
+#include <AL/al.h>
+#include <AL/alc.h>
+#else
+#include <OpenAL/al.h>
+#include <OpenAL/alc.h>
+#endif
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "openal"
+
+/* should be enough for buffer size = 2048 */
+#define NUM_BUFFERS 16
+
+struct OpenALOutput {
+ struct audio_output base;
+
+ const char *device_name;
+ ALCdevice *device;
+ ALCcontext *context;
+ ALuint buffers[NUM_BUFFERS];
+ unsigned filled;
+ ALuint source;
+ ALenum format;
+ ALuint frequency;
+
+ bool Initialize(const config_param &param, GError **error_r) {
+ return ao_base_init(&base, &openal_output_plugin, param,
+ error_r);
+ }
+
+ void Deinitialize() {
+ ao_base_finish(&base);
+ }
+};
+
+static inline GQuark
+openal_output_quark(void)
+{
+ return g_quark_from_static_string("openal_output");
+}
+
+static ALenum
+openal_audio_format(AudioFormat &audio_format)
+{
+ /* note: cannot map SampleFormat::S8 to AL_FORMAT_STEREO8 or
+ AL_FORMAT_MONO8 since OpenAL expects unsigned 8 bit
+ samples, while MPD uses signed samples */
+
+ switch (audio_format.format) {
+ case SampleFormat::S16:
+ if (audio_format.channels == 2)
+ return AL_FORMAT_STEREO16;
+ if (audio_format.channels == 1)
+ return AL_FORMAT_MONO16;
+
+ /* fall back to mono */
+ audio_format.channels = 1;
+ return openal_audio_format(audio_format);
+
+ default:
+ /* fall back to 16 bit */
+ audio_format.format = SampleFormat::S16;
+ return openal_audio_format(audio_format);
+ }
+}
+
+G_GNUC_PURE
+static inline ALint
+openal_get_source_i(const OpenALOutput *od, ALenum param)
+{
+ ALint value;
+ alGetSourcei(od->source, param, &value);
+ return value;
+}
+
+G_GNUC_PURE
+static inline bool
+openal_has_processed(const OpenALOutput *od)
+{
+ return openal_get_source_i(od, AL_BUFFERS_PROCESSED) > 0;
+}
+
+G_GNUC_PURE
+static inline ALint
+openal_is_playing(const OpenALOutput *od)
+{
+ return openal_get_source_i(od, AL_SOURCE_STATE) == AL_PLAYING;
+}
+
+static bool
+openal_setup_context(OpenALOutput *od,
+ GError **error)
+{
+ od->device = alcOpenDevice(od->device_name);
+
+ if (od->device == nullptr) {
+ g_set_error(error, openal_output_quark(), 0,
+ "Error opening OpenAL device \"%s\"\n",
+ od->device_name);
+ return false;
+ }
+
+ od->context = alcCreateContext(od->device, nullptr);
+
+ if (od->context == nullptr) {
+ g_set_error(error, openal_output_quark(), 0,
+ "Error creating context for \"%s\"\n",
+ od->device_name);
+ alcCloseDevice(od->device);
+ return false;
+ }
+
+ return true;
+}
+
+static struct audio_output *
+openal_init(const config_param &param, GError **error_r)
+{
+ const char *device_name = param.GetBlockValue("device");
+ if (device_name == nullptr) {
+ device_name = alcGetString(nullptr, ALC_DEFAULT_DEVICE_SPECIFIER);
+ }
+
+ OpenALOutput *od = new OpenALOutput();
+ if (!od->Initialize(param, error_r)) {
+ delete od;
+ return nullptr;
+ }
+
+ od->device_name = device_name;
+
+ return &od->base;
+}
+
+static void
+openal_finish(struct audio_output *ao)
+{
+ OpenALOutput *od = (OpenALOutput *)ao;
+
+ od->Deinitialize();
+ delete od;
+}
+
+static bool
+openal_open(struct audio_output *ao, AudioFormat &audio_format,
+ GError **error)
+{
+ OpenALOutput *od = (OpenALOutput *)ao;
+
+ od->format = openal_audio_format(audio_format);
+
+ if (!openal_setup_context(od, error)) {
+ return false;
+ }
+
+ alcMakeContextCurrent(od->context);
+ alGenBuffers(NUM_BUFFERS, od->buffers);
+
+ if (alGetError() != AL_NO_ERROR) {
+ g_set_error(error, openal_output_quark(), 0,
+ "Failed to generate buffers");
+ return false;
+ }
+
+ alGenSources(1, &od->source);
+
+ if (alGetError() != AL_NO_ERROR) {
+ g_set_error(error, openal_output_quark(), 0,
+ "Failed to generate source");
+ alDeleteBuffers(NUM_BUFFERS, od->buffers);
+ return false;
+ }
+
+ od->filled = 0;
+ od->frequency = audio_format.sample_rate;
+
+ return true;
+}
+
+static void
+openal_close(struct audio_output *ao)
+{
+ OpenALOutput *od = (OpenALOutput *)ao;
+
+ alcMakeContextCurrent(od->context);
+ alDeleteSources(1, &od->source);
+ alDeleteBuffers(NUM_BUFFERS, od->buffers);
+ alcDestroyContext(od->context);
+ alcCloseDevice(od->device);
+}
+
+static unsigned
+openal_delay(struct audio_output *ao)
+{
+ OpenALOutput *od = (OpenALOutput *)ao;
+
+ return od->filled < NUM_BUFFERS || openal_has_processed(od)
+ ? 0
+ /* we don't know exactly how long we must wait for the
+ next buffer to finish, so this is a random
+ guess: */
+ : 50;
+}
+
+static size_t
+openal_play(struct audio_output *ao, const void *chunk, size_t size,
+ G_GNUC_UNUSED GError **error)
+{
+ OpenALOutput *od = (OpenALOutput *)ao;
+ ALuint buffer;
+
+ if (alcGetCurrentContext() != od->context) {
+ alcMakeContextCurrent(od->context);
+ }
+
+ if (od->filled < NUM_BUFFERS) {
+ /* fill all buffers */
+ buffer = od->buffers[od->filled];
+ od->filled++;
+ } else {
+ /* wait for processed buffer */
+ while (!openal_has_processed(od))
+ g_usleep(10);
+
+ alSourceUnqueueBuffers(od->source, 1, &buffer);
+ }
+
+ alBufferData(buffer, od->format, chunk, size, od->frequency);
+ alSourceQueueBuffers(od->source, 1, &buffer);
+
+ if (!openal_is_playing(od))
+ alSourcePlay(od->source);
+
+ return size;
+}
+
+static void
+openal_cancel(struct audio_output *ao)
+{
+ OpenALOutput *od = (OpenALOutput *)ao;
+
+ od->filled = 0;
+ alcMakeContextCurrent(od->context);
+ alSourceStop(od->source);
+
+ /* force-unqueue all buffers */
+ alSourcei(od->source, AL_BUFFER, 0);
+ od->filled = 0;
+}
+
+const struct audio_output_plugin openal_output_plugin = {
+ "openal",
+ nullptr,
+ openal_init,
+ openal_finish,
+ nullptr,
+ nullptr,
+ openal_open,
+ openal_close,
+ openal_delay,
+ nullptr,
+ openal_play,
+ nullptr,
+ openal_cancel,
+ nullptr,
+ nullptr,
+};
diff --git a/src/output/OpenALOutputPlugin.hxx b/src/output/OpenALOutputPlugin.hxx
new file mode 100644
index 000000000..e1ebf3d4f
--- /dev/null
+++ b/src/output/OpenALOutputPlugin.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_OPENAL_OUTPUT_PLUGIN_HXX
+#define MPD_OPENAL_OUTPUT_PLUGIN_HXX
+
+extern const struct audio_output_plugin openal_output_plugin;
+
+#endif
diff --git a/src/output/OssOutputPlugin.cxx b/src/output/OssOutputPlugin.cxx
new file mode 100644
index 000000000..2ef0edd67
--- /dev/null
+++ b/src/output/OssOutputPlugin.cxx
@@ -0,0 +1,787 @@
+/*
+ * 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 "OssOutputPlugin.hxx"
+#include "OutputAPI.hxx"
+#include "MixerList.hxx"
+#include "fd_util.h"
+
+#include <glib.h>
+
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <assert.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "oss"
+
+#if defined(__OpenBSD__) || defined(__NetBSD__)
+# include <soundcard.h>
+#else /* !(defined(__OpenBSD__) || defined(__NetBSD__) */
+# include <sys/soundcard.h>
+#endif /* !(defined(__OpenBSD__) || defined(__NetBSD__) */
+
+/* We got bug reports from FreeBSD users who said that the two 24 bit
+ formats generate white noise on FreeBSD, but 32 bit works. This is
+ a workaround until we know what exactly is expected by the kernel
+ audio drivers. */
+#ifndef __linux__
+#undef AFMT_S24_PACKED
+#undef AFMT_S24_NE
+#endif
+
+#ifdef AFMT_S24_PACKED
+#include "pcm/PcmExport.hxx"
+#include "util/Manual.hxx"
+#endif
+
+struct OssOutput {
+ struct audio_output base;
+
+#ifdef AFMT_S24_PACKED
+ Manual<PcmExport> pcm_export;
+#endif
+
+ int fd;
+ const char *device;
+
+ /**
+ * The current input audio format. This is needed to reopen
+ * the device after cancel().
+ */
+ AudioFormat audio_format;
+
+ /**
+ * The current OSS audio format. This is needed to reopen the
+ * device after cancel().
+ */
+ int oss_format;
+
+ OssOutput():fd(-1), device(nullptr) {}
+
+ bool Initialize(const config_param &param, GError **error_r) {
+ return ao_base_init(&base, &oss_output_plugin, param,
+ error_r);
+ }
+
+ void Deinitialize() {
+ ao_base_finish(&base);
+ }
+};
+
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+oss_output_quark(void)
+{
+ return g_quark_from_static_string("oss_output");
+}
+
+enum oss_stat {
+ OSS_STAT_NO_ERROR = 0,
+ OSS_STAT_NOT_CHAR_DEV = -1,
+ OSS_STAT_NO_PERMS = -2,
+ OSS_STAT_DOESN_T_EXIST = -3,
+ OSS_STAT_OTHER = -4,
+};
+
+static enum oss_stat
+oss_stat_device(const char *device, int *errno_r)
+{
+ struct stat st;
+
+ if (0 == stat(device, &st)) {
+ if (!S_ISCHR(st.st_mode)) {
+ return OSS_STAT_NOT_CHAR_DEV;
+ }
+ } else {
+ *errno_r = errno;
+
+ switch (errno) {
+ case ENOENT:
+ case ENOTDIR:
+ return OSS_STAT_DOESN_T_EXIST;
+ case EACCES:
+ return OSS_STAT_NO_PERMS;
+ default:
+ return OSS_STAT_OTHER;
+ }
+ }
+
+ return OSS_STAT_NO_ERROR;
+}
+
+static const char *default_devices[] = { "/dev/sound/dsp", "/dev/dsp" };
+
+static bool
+oss_output_test_default_device(void)
+{
+ int fd, i;
+
+ for (i = G_N_ELEMENTS(default_devices); --i >= 0; ) {
+ fd = open_cloexec(default_devices[i], O_WRONLY, 0);
+
+ if (fd >= 0) {
+ close(fd);
+ return true;
+ }
+ g_warning("Error opening OSS device \"%s\": %s\n",
+ default_devices[i], g_strerror(errno));
+ }
+
+ return false;
+}
+
+static struct audio_output *
+oss_open_default(GError **error)
+{
+ int err[G_N_ELEMENTS(default_devices)];
+ enum oss_stat ret[G_N_ELEMENTS(default_devices)];
+
+ const config_param empty;
+ for (int i = G_N_ELEMENTS(default_devices); --i >= 0; ) {
+ ret[i] = oss_stat_device(default_devices[i], &err[i]);
+ if (ret[i] == OSS_STAT_NO_ERROR) {
+ OssOutput *od = new OssOutput();
+ if (!od->Initialize(empty, error)) {
+ delete od;
+ return NULL;
+ }
+
+ od->device = default_devices[i];
+ return &od->base;
+ }
+ }
+
+ for (int i = G_N_ELEMENTS(default_devices); --i >= 0; ) {
+ const char *dev = default_devices[i];
+ switch(ret[i]) {
+ case OSS_STAT_NO_ERROR:
+ /* never reached */
+ break;
+ case OSS_STAT_DOESN_T_EXIST:
+ g_warning("%s not found\n", dev);
+ break;
+ case OSS_STAT_NOT_CHAR_DEV:
+ g_warning("%s is not a character device\n", dev);
+ break;
+ case OSS_STAT_NO_PERMS:
+ g_warning("%s: permission denied\n", dev);
+ break;
+ case OSS_STAT_OTHER:
+ g_warning("Error accessing %s: %s\n",
+ dev, g_strerror(err[i]));
+ }
+ }
+
+ g_set_error(error, oss_output_quark(), 0,
+ "error trying to open default OSS device");
+ return NULL;
+}
+
+static struct audio_output *
+oss_output_init(const config_param &param, GError **error_r)
+{
+ const char *device = param.GetBlockValue("device");
+ if (device != NULL) {
+ OssOutput *od = new OssOutput();
+ if (!od->Initialize(param, error_r)) {
+ delete od;
+ return NULL;
+ }
+
+ od->device = device;
+ return &od->base;
+ }
+
+ return oss_open_default(error_r);
+}
+
+static void
+oss_output_finish(struct audio_output *ao)
+{
+ OssOutput *od = (OssOutput *)ao;
+
+ ao_base_finish(&od->base);
+ delete od;
+}
+
+#ifdef AFMT_S24_PACKED
+
+static bool
+oss_output_enable(struct audio_output *ao, G_GNUC_UNUSED GError **error_r)
+{
+ OssOutput *od = (OssOutput *)ao;
+
+ od->pcm_export.Construct();
+ return true;
+}
+
+static void
+oss_output_disable(struct audio_output *ao)
+{
+ OssOutput *od = (OssOutput *)ao;
+
+ od->pcm_export.Destruct();
+}
+
+#endif
+
+static void
+oss_close(OssOutput *od)
+{
+ if (od->fd >= 0)
+ close(od->fd);
+ od->fd = -1;
+}
+
+/**
+ * A tri-state type for oss_try_ioctl().
+ */
+enum oss_setup_result {
+ SUCCESS,
+ ERROR,
+ UNSUPPORTED,
+};
+
+/**
+ * Invoke an ioctl on the OSS file descriptor. On success, SUCCESS is
+ * returned. If the parameter is not supported, UNSUPPORTED is
+ * returned. Any other failure returns ERROR and allocates a GError.
+ */
+static enum oss_setup_result
+oss_try_ioctl_r(int fd, unsigned long request, int *value_r,
+ const char *msg, GError **error_r)
+{
+ assert(fd >= 0);
+ assert(value_r != NULL);
+ assert(msg != NULL);
+ assert(error_r == NULL || *error_r == NULL);
+
+ int ret = ioctl(fd, request, value_r);
+ if (ret >= 0)
+ return SUCCESS;
+
+ if (errno == EINVAL)
+ return UNSUPPORTED;
+
+ g_set_error(error_r, oss_output_quark(), errno,
+ "%s: %s", msg, g_strerror(errno));
+ return ERROR;
+}
+
+/**
+ * Invoke an ioctl on the OSS file descriptor. On success, SUCCESS is
+ * returned. If the parameter is not supported, UNSUPPORTED is
+ * returned. Any other failure returns ERROR and allocates a GError.
+ */
+static enum oss_setup_result
+oss_try_ioctl(int fd, unsigned long request, int value,
+ const char *msg, GError **error_r)
+{
+ return oss_try_ioctl_r(fd, request, &value, msg, error_r);
+}
+
+/**
+ * Set up the channel number, and attempts to find alternatives if the
+ * specified number is not supported.
+ */
+static bool
+oss_setup_channels(int fd, AudioFormat &audio_format, GError **error_r)
+{
+ const char *const msg = "Failed to set channel count";
+ int channels = audio_format.channels;
+ enum oss_setup_result result =
+ oss_try_ioctl_r(fd, SNDCTL_DSP_CHANNELS, &channels, msg, error_r);
+ switch (result) {
+ case SUCCESS:
+ if (!audio_valid_channel_count(channels))
+ break;
+
+ audio_format.channels = channels;
+ return true;
+
+ case ERROR:
+ return false;
+
+ case UNSUPPORTED:
+ break;
+ }
+
+ for (unsigned i = 1; i < 2; ++i) {
+ if (i == audio_format.channels)
+ /* don't try that again */
+ continue;
+
+ channels = i;
+ result = oss_try_ioctl_r(fd, SNDCTL_DSP_CHANNELS, &channels,
+ msg, error_r);
+ switch (result) {
+ case SUCCESS:
+ if (!audio_valid_channel_count(channels))
+ break;
+
+ audio_format.channels = channels;
+ return true;
+
+ case ERROR:
+ return false;
+
+ case UNSUPPORTED:
+ break;
+ }
+ }
+
+ g_set_error(error_r, oss_output_quark(), EINVAL, "%s", msg);
+ return false;
+}
+
+/**
+ * Set up the sample rate, and attempts to find alternatives if the
+ * specified sample rate is not supported.
+ */
+static bool
+oss_setup_sample_rate(int fd, AudioFormat &audio_format,
+ GError **error_r)
+{
+ const char *const msg = "Failed to set sample rate";
+ int sample_rate = audio_format.sample_rate;
+ enum oss_setup_result result =
+ oss_try_ioctl_r(fd, SNDCTL_DSP_SPEED, &sample_rate,
+ msg, error_r);
+ switch (result) {
+ case SUCCESS:
+ if (!audio_valid_sample_rate(sample_rate))
+ break;
+
+ audio_format.sample_rate = sample_rate;
+ return true;
+
+ case ERROR:
+ return false;
+
+ case UNSUPPORTED:
+ break;
+ }
+
+ static const int sample_rates[] = { 48000, 44100, 0 };
+ for (unsigned i = 0; sample_rates[i] != 0; ++i) {
+ sample_rate = sample_rates[i];
+ if (sample_rate == (int)audio_format.sample_rate)
+ continue;
+
+ result = oss_try_ioctl_r(fd, SNDCTL_DSP_SPEED, &sample_rate,
+ msg, error_r);
+ switch (result) {
+ case SUCCESS:
+ if (!audio_valid_sample_rate(sample_rate))
+ break;
+
+ audio_format.sample_rate = sample_rate;
+ return true;
+
+ case ERROR:
+ return false;
+
+ case UNSUPPORTED:
+ break;
+ }
+ }
+
+ g_set_error(error_r, oss_output_quark(), EINVAL, "%s", msg);
+ return false;
+}
+
+/**
+ * Convert a MPD sample format to its OSS counterpart. Returns
+ * AFMT_QUERY if there is no direct counterpart.
+ */
+static int
+sample_format_to_oss(SampleFormat format)
+{
+ switch (format) {
+ case SampleFormat::UNDEFINED:
+ case SampleFormat::FLOAT:
+ case SampleFormat::DSD:
+ return AFMT_QUERY;
+
+ case SampleFormat::S8:
+ return AFMT_S8;
+
+ case SampleFormat::S16:
+ return AFMT_S16_NE;
+
+ case SampleFormat::S24_P32:
+#ifdef AFMT_S24_NE
+ return AFMT_S24_NE;
+#else
+ return AFMT_QUERY;
+#endif
+
+ case SampleFormat::S32:
+#ifdef AFMT_S32_NE
+ return AFMT_S32_NE;
+#else
+ return AFMT_QUERY;
+#endif
+ }
+
+ return AFMT_QUERY;
+}
+
+/**
+ * Convert an OSS sample format to its MPD counterpart. Returns
+ * SampleFormat::UNDEFINED if there is no direct counterpart.
+ */
+static SampleFormat
+sample_format_from_oss(int format)
+{
+ switch (format) {
+ case AFMT_S8:
+ return SampleFormat::S8;
+
+ case AFMT_S16_NE:
+ return SampleFormat::S16;
+
+#ifdef AFMT_S24_PACKED
+ case AFMT_S24_PACKED:
+ return SampleFormat::S24_P32;
+#endif
+
+#ifdef AFMT_S24_NE
+ case AFMT_S24_NE:
+ return SampleFormat::S24_P32;
+#endif
+
+#ifdef AFMT_S32_NE
+ case AFMT_S32_NE:
+ return SampleFormat::S32;
+#endif
+
+ default:
+ return SampleFormat::UNDEFINED;
+ }
+}
+
+/**
+ * Probe one sample format.
+ *
+ * @return the selected sample format or SampleFormat::UNDEFINED on
+ * error
+ */
+static enum oss_setup_result
+oss_probe_sample_format(int fd, SampleFormat sample_format,
+ SampleFormat *sample_format_r,
+ int *oss_format_r,
+#ifdef AFMT_S24_PACKED
+ PcmExport &pcm_export,
+#endif
+ GError **error_r)
+{
+ int oss_format = sample_format_to_oss(sample_format);
+ if (oss_format == AFMT_QUERY)
+ return UNSUPPORTED;
+
+ enum oss_setup_result result =
+ oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE,
+ &oss_format,
+ "Failed to set sample format", error_r);
+
+#ifdef AFMT_S24_PACKED
+ if (result == UNSUPPORTED && sample_format == SampleFormat::S24_P32) {
+ /* if the driver doesn't support padded 24 bit, try
+ packed 24 bit */
+ oss_format = AFMT_S24_PACKED;
+ result = oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE,
+ &oss_format,
+ "Failed to set sample format", error_r);
+ }
+#endif
+
+ if (result != SUCCESS)
+ return result;
+
+ sample_format = sample_format_from_oss(oss_format);
+ if (sample_format == SampleFormat::UNDEFINED)
+ return UNSUPPORTED;
+
+ *sample_format_r = sample_format;
+ *oss_format_r = oss_format;
+
+#ifdef AFMT_S24_PACKED
+ pcm_export.Open(sample_format, 0, false, false,
+ oss_format == AFMT_S24_PACKED,
+ oss_format == AFMT_S24_PACKED &&
+ G_BYTE_ORDER != G_LITTLE_ENDIAN);
+#endif
+
+ return SUCCESS;
+}
+
+/**
+ * Set up the sample format, and attempts to find alternatives if the
+ * specified format is not supported.
+ */
+static bool
+oss_setup_sample_format(int fd, AudioFormat &audio_format,
+ int *oss_format_r,
+#ifdef AFMT_S24_PACKED
+ PcmExport &pcm_export,
+#endif
+ GError **error_r)
+{
+ SampleFormat mpd_format;
+ enum oss_setup_result result =
+ oss_probe_sample_format(fd, audio_format.format,
+ &mpd_format, oss_format_r,
+#ifdef AFMT_S24_PACKED
+ pcm_export,
+#endif
+ error_r);
+ switch (result) {
+ case SUCCESS:
+ audio_format.format = mpd_format;
+ return true;
+
+ case ERROR:
+ return false;
+
+ case UNSUPPORTED:
+ break;
+ }
+
+ if (result != UNSUPPORTED)
+ return result == SUCCESS;
+
+ /* the requested sample format is not available - probe for
+ other formats supported by MPD */
+
+ static const SampleFormat sample_formats[] = {
+ SampleFormat::S24_P32,
+ SampleFormat::S32,
+ SampleFormat::S16,
+ SampleFormat::S8,
+ SampleFormat::UNDEFINED /* sentinel */
+ };
+
+ for (unsigned i = 0; sample_formats[i] != SampleFormat::UNDEFINED; ++i) {
+ mpd_format = sample_formats[i];
+ if (mpd_format == audio_format.format)
+ /* don't try that again */
+ continue;
+
+ result = oss_probe_sample_format(fd, mpd_format,
+ &mpd_format, oss_format_r,
+#ifdef AFMT_S24_PACKED
+ pcm_export,
+#endif
+ error_r);
+ switch (result) {
+ case SUCCESS:
+ audio_format.format = mpd_format;
+ return true;
+
+ case ERROR:
+ return false;
+
+ case UNSUPPORTED:
+ break;
+ }
+ }
+
+ g_set_error_literal(error_r, oss_output_quark(), EINVAL,
+ "Failed to set sample format");
+ return false;
+}
+
+/**
+ * Sets up the OSS device which was opened before.
+ */
+static bool
+oss_setup(OssOutput *od, AudioFormat &audio_format,
+ GError **error_r)
+{
+ return oss_setup_channels(od->fd, audio_format, error_r) &&
+ oss_setup_sample_rate(od->fd, audio_format, error_r) &&
+ oss_setup_sample_format(od->fd, audio_format, &od->oss_format,
+#ifdef AFMT_S24_PACKED
+ od->pcm_export,
+#endif
+ error_r);
+}
+
+/**
+ * Reopen the device with the saved audio_format, without any probing.
+ */
+static bool
+oss_reopen(OssOutput *od, GError **error_r)
+{
+ assert(od->fd < 0);
+
+ od->fd = open_cloexec(od->device, O_WRONLY, 0);
+ if (od->fd < 0) {
+ g_set_error(error_r, oss_output_quark(), errno,
+ "Error opening OSS device \"%s\": %s",
+ od->device, g_strerror(errno));
+ return false;
+ }
+
+ enum oss_setup_result result;
+
+ const char *const msg1 = "Failed to set channel count";
+ result = oss_try_ioctl(od->fd, SNDCTL_DSP_CHANNELS,
+ od->audio_format.channels, msg1, error_r);
+ if (result != SUCCESS) {
+ oss_close(od);
+ if (result == UNSUPPORTED)
+ g_set_error(error_r, oss_output_quark(), EINVAL,
+ "%s", msg1);
+ return false;
+ }
+
+ const char *const msg2 = "Failed to set sample rate";
+ result = oss_try_ioctl(od->fd, SNDCTL_DSP_SPEED,
+ od->audio_format.sample_rate, msg2, error_r);
+ if (result != SUCCESS) {
+ oss_close(od);
+ if (result == UNSUPPORTED)
+ g_set_error(error_r, oss_output_quark(), EINVAL,
+ "%s", msg2);
+ return false;
+ }
+
+ const char *const msg3 = "Failed to set sample format";
+ result = oss_try_ioctl(od->fd, SNDCTL_DSP_SAMPLESIZE,
+ od->oss_format,
+ msg3, error_r);
+ if (result != SUCCESS) {
+ oss_close(od);
+ if (result == UNSUPPORTED)
+ g_set_error(error_r, oss_output_quark(), EINVAL,
+ "%s", msg3);
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+oss_output_open(struct audio_output *ao, AudioFormat &audio_format,
+ GError **error)
+{
+ OssOutput *od = (OssOutput *)ao;
+
+ od->fd = open_cloexec(od->device, O_WRONLY, 0);
+ if (od->fd < 0) {
+ g_set_error(error, oss_output_quark(), errno,
+ "Error opening OSS device \"%s\": %s",
+ od->device, g_strerror(errno));
+ return false;
+ }
+
+ if (!oss_setup(od, audio_format, error)) {
+ oss_close(od);
+ return false;
+ }
+
+ od->audio_format = audio_format;
+ return true;
+}
+
+static void
+oss_output_close(struct audio_output *ao)
+{
+ OssOutput *od = (OssOutput *)ao;
+
+ oss_close(od);
+}
+
+static void
+oss_output_cancel(struct audio_output *ao)
+{
+ OssOutput *od = (OssOutput *)ao;
+
+ if (od->fd >= 0) {
+ ioctl(od->fd, SNDCTL_DSP_RESET, 0);
+ oss_close(od);
+ }
+}
+
+static size_t
+oss_output_play(struct audio_output *ao, const void *chunk, size_t size,
+ GError **error)
+{
+ OssOutput *od = (OssOutput *)ao;
+ ssize_t ret;
+
+ /* reopen the device since it was closed by dropBufferedAudio */
+ if (od->fd < 0 && !oss_reopen(od, error))
+ return 0;
+
+#ifdef AFMT_S24_PACKED
+ chunk = od->pcm_export->Export(chunk, size, size);
+#endif
+
+ while (true) {
+ ret = write(od->fd, chunk, size);
+ if (ret > 0) {
+#ifdef AFMT_S24_PACKED
+ ret = od->pcm_export->CalcSourceSize(ret);
+#endif
+ return ret;
+ }
+
+ if (ret < 0 && errno != EINTR) {
+ g_set_error(error, oss_output_quark(), errno,
+ "Write error on %s: %s",
+ od->device, g_strerror(errno));
+ return 0;
+ }
+ }
+}
+
+const struct audio_output_plugin oss_output_plugin = {
+ "oss",
+ oss_output_test_default_device,
+ oss_output_init,
+ oss_output_finish,
+#ifdef AFMT_S24_PACKED
+ oss_output_enable,
+ oss_output_disable,
+#else
+ nullptr,
+ nullptr,
+#endif
+ oss_output_open,
+ oss_output_close,
+ nullptr,
+ nullptr,
+ oss_output_play,
+ nullptr,
+ oss_output_cancel,
+ nullptr,
+
+ &oss_mixer_plugin,
+};
diff --git a/src/output/OssOutputPlugin.hxx b/src/output/OssOutputPlugin.hxx
new file mode 100644
index 000000000..6c5c9530b
--- /dev/null
+++ b/src/output/OssOutputPlugin.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_OSS_OUTPUT_PLUGIN_HXX
+#define MPD_OSS_OUTPUT_PLUGIN_HXX
+
+extern const struct audio_output_plugin oss_output_plugin;
+
+#endif
diff --git a/src/output/PipeOutputPlugin.cxx b/src/output/PipeOutputPlugin.cxx
new file mode 100644
index 000000000..f485f1554
--- /dev/null
+++ b/src/output/PipeOutputPlugin.cxx
@@ -0,0 +1,152 @@
+/*
+ * 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 "PipeOutputPlugin.hxx"
+#include "OutputAPI.hxx"
+
+#include <stdio.h>
+#include <errno.h>
+
+struct PipeOutput {
+ struct audio_output base;
+
+ char *cmd;
+ FILE *fh;
+
+ bool Initialize(const config_param &param, GError **error_r) {
+ return ao_base_init(&base, &pipe_output_plugin, param,
+ error_r);
+ }
+
+ void Deinitialize() {
+ ao_base_finish(&base);
+ }
+
+ bool Configure(const config_param &param, GError **error_r);
+};
+
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+pipe_output_quark(void)
+{
+ return g_quark_from_static_string("pipe_output");
+}
+
+inline bool
+PipeOutput::Configure(const config_param &param, GError **error_r)
+{
+ cmd = param.DupBlockString("command");
+ if (cmd == nullptr) {
+ g_set_error(error_r, pipe_output_quark(), 0,
+ "No \"command\" parameter specified");
+ return false;
+ }
+
+ return true;
+}
+
+static struct audio_output *
+pipe_output_init(const config_param &param, GError **error_r)
+{
+ PipeOutput *pd = new PipeOutput();
+
+ if (!pd->Initialize(param, error_r)) {
+ delete pd;
+ return nullptr;
+ }
+
+ if (!pd->Configure(param, error_r)) {
+ pd->Deinitialize();
+ delete pd;
+ return nullptr;
+ }
+
+ return &pd->base;
+}
+
+static void
+pipe_output_finish(struct audio_output *ao)
+{
+ PipeOutput *pd = (PipeOutput *)ao;
+
+ g_free(pd->cmd);
+ pd->Deinitialize();
+ delete pd;
+}
+
+static bool
+pipe_output_open(struct audio_output *ao,
+ G_GNUC_UNUSED AudioFormat &audio_format,
+ G_GNUC_UNUSED GError **error)
+{
+ PipeOutput *pd = (PipeOutput *)ao;
+
+ pd->fh = popen(pd->cmd, "w");
+ if (pd->fh == nullptr) {
+ g_set_error(error, pipe_output_quark(), errno,
+ "Error opening pipe \"%s\": %s",
+ pd->cmd, g_strerror(errno));
+ return false;
+ }
+
+ return true;
+}
+
+static void
+pipe_output_close(struct audio_output *ao)
+{
+ PipeOutput *pd = (PipeOutput *)ao;
+
+ pclose(pd->fh);
+}
+
+static size_t
+pipe_output_play(struct audio_output *ao, const void *chunk, size_t size, GError **error)
+{
+ PipeOutput *pd = (PipeOutput *)ao;
+ size_t ret;
+
+ ret = fwrite(chunk, 1, size, pd->fh);
+ if (ret == 0)
+ g_set_error(error, pipe_output_quark(), errno,
+ "Write error on pipe: %s", g_strerror(errno));
+
+ return ret;
+}
+
+const struct audio_output_plugin pipe_output_plugin = {
+ "pipe",
+ nullptr,
+ pipe_output_init,
+ pipe_output_finish,
+ nullptr,
+ nullptr,
+ pipe_output_open,
+ pipe_output_close,
+ nullptr,
+ nullptr,
+ pipe_output_play,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+};
diff --git a/src/output/PipeOutputPlugin.hxx b/src/output/PipeOutputPlugin.hxx
new file mode 100644
index 000000000..f0c29706b
--- /dev/null
+++ b/src/output/PipeOutputPlugin.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_PIPE_OUTPUT_PLUGIN_HXX
+#define MPD_PIPE_OUTPUT_PLUGIN_HXX
+
+extern const struct audio_output_plugin pipe_output_plugin;
+
+#endif
diff --git a/src/output/PulseOutputPlugin.cxx b/src/output/PulseOutputPlugin.cxx
new file mode 100644
index 000000000..f59c8d76e
--- /dev/null
+++ b/src/output/PulseOutputPlugin.cxx
@@ -0,0 +1,956 @@
+/*
+ * 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 "PulseOutputPlugin.hxx"
+#include "OutputAPI.hxx"
+#include "MixerList.hxx"
+#include "mixer/PulseMixerPlugin.hxx"
+
+#include <glib.h>
+
+#include <pulse/thread-mainloop.h>
+#include <pulse/context.h>
+#include <pulse/stream.h>
+#include <pulse/introspect.h>
+#include <pulse/subscribe.h>
+#include <pulse/error.h>
+#include <pulse/version.h>
+
+#include <assert.h>
+#include <stddef.h>
+
+#define MPD_PULSE_NAME "Music Player Daemon"
+
+#if !defined(PA_CHECK_VERSION)
+/**
+ * This macro was implemented in libpulse 0.9.16.
+ */
+#define PA_CHECK_VERSION(a,b,c) false
+#endif
+
+struct PulseOutput {
+ struct audio_output base;
+
+ const char *name;
+ const char *server;
+ const char *sink;
+
+ PulseMixer *mixer;
+
+ struct pa_threaded_mainloop *mainloop;
+ struct pa_context *context;
+ struct pa_stream *stream;
+
+ size_t writable;
+
+#if !PA_CHECK_VERSION(0,9,11)
+ /**
+ * We need this variable because pa_stream_is_corked() wasn't
+ * added before 0.9.11.
+ */
+ bool pause;
+#endif
+};
+
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+pulse_output_quark(void)
+{
+ return g_quark_from_static_string("pulse_output");
+}
+
+void
+pulse_output_lock(PulseOutput *po)
+{
+ pa_threaded_mainloop_lock(po->mainloop);
+}
+
+void
+pulse_output_unlock(PulseOutput *po)
+{
+ pa_threaded_mainloop_unlock(po->mainloop);
+}
+
+void
+pulse_output_set_mixer(PulseOutput *po, PulseMixer *pm)
+{
+ assert(po != nullptr);
+ assert(po->mixer == nullptr);
+ assert(pm != nullptr);
+
+ po->mixer = pm;
+
+ if (po->mainloop == nullptr)
+ return;
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ if (po->context != nullptr &&
+ pa_context_get_state(po->context) == PA_CONTEXT_READY) {
+ pulse_mixer_on_connect(pm, po->context);
+
+ if (po->stream != nullptr &&
+ pa_stream_get_state(po->stream) == PA_STREAM_READY)
+ pulse_mixer_on_change(pm, po->context, po->stream);
+ }
+
+ pa_threaded_mainloop_unlock(po->mainloop);
+}
+
+void
+pulse_output_clear_mixer(PulseOutput *po, gcc_unused PulseMixer *pm)
+{
+ assert(po != nullptr);
+ assert(pm != nullptr);
+ assert(po->mixer == pm);
+
+ po->mixer = nullptr;
+}
+
+bool
+pulse_output_set_volume(PulseOutput *po,
+ const struct pa_cvolume *volume, GError **error_r)
+{
+ pa_operation *o;
+
+ if (po->context == nullptr || po->stream == nullptr ||
+ pa_stream_get_state(po->stream) != PA_STREAM_READY) {
+ g_set_error(error_r, pulse_output_quark(), 0, "disconnected");
+ return false;
+ }
+
+ o = pa_context_set_sink_input_volume(po->context,
+ pa_stream_get_index(po->stream),
+ volume, nullptr, nullptr);
+ if (o == nullptr) {
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "failed to set PulseAudio volume: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ return false;
+ }
+
+ pa_operation_unref(o);
+ return true;
+}
+
+/**
+ * \brief waits for a pulseaudio operation to finish, frees it and
+ * unlocks the mainloop
+ * \param operation the operation to wait for
+ * \return true if operation has finished normally (DONE state),
+ * false otherwise
+ */
+static bool
+pulse_wait_for_operation(struct pa_threaded_mainloop *mainloop,
+ struct pa_operation *operation)
+{
+ pa_operation_state_t state;
+
+ assert(mainloop != nullptr);
+ assert(operation != nullptr);
+
+ state = pa_operation_get_state(operation);
+ while (state == PA_OPERATION_RUNNING) {
+ pa_threaded_mainloop_wait(mainloop);
+ state = pa_operation_get_state(operation);
+ }
+
+ pa_operation_unref(operation);
+
+ return state == PA_OPERATION_DONE;
+}
+
+/**
+ * Callback function for stream operation. It just sends a signal to
+ * the caller thread, to wake pulse_wait_for_operation() up.
+ */
+static void
+pulse_output_stream_success_cb(G_GNUC_UNUSED pa_stream *s,
+ G_GNUC_UNUSED int success, void *userdata)
+{
+ PulseOutput *po = (PulseOutput *)userdata;
+
+ pa_threaded_mainloop_signal(po->mainloop, 0);
+}
+
+static void
+pulse_output_context_state_cb(struct pa_context *context, void *userdata)
+{
+ PulseOutput *po = (PulseOutput *)userdata;
+
+ switch (pa_context_get_state(context)) {
+ case PA_CONTEXT_READY:
+ if (po->mixer != nullptr)
+ pulse_mixer_on_connect(po->mixer, context);
+
+ pa_threaded_mainloop_signal(po->mainloop, 0);
+ break;
+
+ case PA_CONTEXT_TERMINATED:
+ case PA_CONTEXT_FAILED:
+ if (po->mixer != nullptr)
+ pulse_mixer_on_disconnect(po->mixer);
+
+ /* the caller thread might be waiting for these
+ states */
+ pa_threaded_mainloop_signal(po->mainloop, 0);
+ break;
+
+ case PA_CONTEXT_UNCONNECTED:
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ break;
+ }
+}
+
+static void
+pulse_output_subscribe_cb(pa_context *context,
+ pa_subscription_event_type_t t,
+ uint32_t idx, void *userdata)
+{
+ PulseOutput *po = (PulseOutput *)userdata;
+ pa_subscription_event_type_t facility =
+ pa_subscription_event_type_t(t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK);
+ pa_subscription_event_type_t type =
+ pa_subscription_event_type_t(t & PA_SUBSCRIPTION_EVENT_TYPE_MASK);
+
+ if (po->mixer != nullptr &&
+ facility == PA_SUBSCRIPTION_EVENT_SINK_INPUT &&
+ po->stream != nullptr &&
+ pa_stream_get_state(po->stream) == PA_STREAM_READY &&
+ idx == pa_stream_get_index(po->stream) &&
+ (type == PA_SUBSCRIPTION_EVENT_NEW ||
+ type == PA_SUBSCRIPTION_EVENT_CHANGE))
+ pulse_mixer_on_change(po->mixer, context, po->stream);
+}
+
+/**
+ * Attempt to connect asynchronously to the PulseAudio server.
+ *
+ * @return true on success, false on error
+ */
+static bool
+pulse_output_connect(PulseOutput *po, GError **error_r)
+{
+ assert(po != nullptr);
+ assert(po->context != nullptr);
+
+ int error;
+
+ error = pa_context_connect(po->context, po->server,
+ (pa_context_flags_t)0, nullptr);
+ if (error < 0) {
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "pa_context_connect() has failed: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Frees and clears the stream.
+ */
+static void
+pulse_output_delete_stream(PulseOutput *po)
+{
+ assert(po != nullptr);
+ assert(po->stream != nullptr);
+
+#if PA_CHECK_VERSION(0,9,8)
+ pa_stream_set_suspended_callback(po->stream, nullptr, nullptr);
+#endif
+
+ pa_stream_set_state_callback(po->stream, nullptr, nullptr);
+ pa_stream_set_write_callback(po->stream, nullptr, nullptr);
+
+ pa_stream_disconnect(po->stream);
+ pa_stream_unref(po->stream);
+ po->stream = nullptr;
+}
+
+/**
+ * Frees and clears the context.
+ *
+ * Caller must lock the main loop.
+ */
+static void
+pulse_output_delete_context(PulseOutput *po)
+{
+ assert(po != nullptr);
+ assert(po->context != nullptr);
+
+ pa_context_set_state_callback(po->context, nullptr, nullptr);
+ pa_context_set_subscribe_callback(po->context, nullptr, nullptr);
+
+ pa_context_disconnect(po->context);
+ pa_context_unref(po->context);
+ po->context = nullptr;
+}
+
+/**
+ * Create, set up and connect a context.
+ *
+ * Caller must lock the main loop.
+ *
+ * @return true on success, false on error
+ */
+static bool
+pulse_output_setup_context(PulseOutput *po, GError **error_r)
+{
+ assert(po != nullptr);
+ assert(po->mainloop != nullptr);
+
+ po->context = pa_context_new(pa_threaded_mainloop_get_api(po->mainloop),
+ MPD_PULSE_NAME);
+ if (po->context == nullptr) {
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "pa_context_new() has failed");
+ return false;
+ }
+
+ pa_context_set_state_callback(po->context,
+ pulse_output_context_state_cb, po);
+ pa_context_set_subscribe_callback(po->context,
+ pulse_output_subscribe_cb, po);
+
+ if (!pulse_output_connect(po, error_r)) {
+ pulse_output_delete_context(po);
+ return false;
+ }
+
+ return true;
+}
+
+static struct audio_output *
+pulse_output_init(const config_param &param, GError **error_r)
+{
+ PulseOutput *po;
+
+ g_setenv("PULSE_PROP_media.role", "music", true);
+
+ po = new PulseOutput();
+ if (!ao_base_init(&po->base, &pulse_output_plugin, param, error_r)) {
+ delete po;
+ return nullptr;
+ }
+
+ po->name = param.GetBlockValue("name", "mpd_pulse");
+ po->server = param.GetBlockValue("server");
+ po->sink = param.GetBlockValue("sink");
+
+ po->mixer = nullptr;
+ po->mainloop = nullptr;
+ po->context = nullptr;
+ po->stream = nullptr;
+
+ return &po->base;
+}
+
+static void
+pulse_output_finish(struct audio_output *ao)
+{
+ PulseOutput *po = (PulseOutput *)ao;
+
+ ao_base_finish(&po->base);
+ delete po;
+}
+
+static bool
+pulse_output_enable(struct audio_output *ao, GError **error_r)
+{
+ PulseOutput *po = (PulseOutput *)ao;
+
+ assert(po->mainloop == nullptr);
+ assert(po->context == nullptr);
+
+ /* create the libpulse mainloop and start the thread */
+
+ po->mainloop = pa_threaded_mainloop_new();
+ if (po->mainloop == nullptr) {
+ g_free(po);
+
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "pa_threaded_mainloop_new() has failed");
+ return false;
+ }
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ if (pa_threaded_mainloop_start(po->mainloop) < 0) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ pa_threaded_mainloop_free(po->mainloop);
+ po->mainloop = nullptr;
+
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "pa_threaded_mainloop_start() has failed");
+ return false;
+ }
+
+ /* create the libpulse context and connect it */
+
+ if (!pulse_output_setup_context(po, error_r)) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ pa_threaded_mainloop_stop(po->mainloop);
+ pa_threaded_mainloop_free(po->mainloop);
+ po->mainloop = nullptr;
+ return false;
+ }
+
+ pa_threaded_mainloop_unlock(po->mainloop);
+
+ return true;
+}
+
+static void
+pulse_output_disable(struct audio_output *ao)
+{
+ PulseOutput *po = (PulseOutput *)ao;
+
+ assert(po->mainloop != nullptr);
+
+ pa_threaded_mainloop_stop(po->mainloop);
+ if (po->context != nullptr)
+ pulse_output_delete_context(po);
+ pa_threaded_mainloop_free(po->mainloop);
+ po->mainloop = nullptr;
+}
+
+/**
+ * Check if the context is (already) connected, and waits if not. If
+ * the context has been disconnected, retry to connect.
+ *
+ * Caller must lock the main loop.
+ *
+ * @return true on success, false on error
+ */
+static bool
+pulse_output_wait_connection(PulseOutput *po, GError **error_r)
+{
+ assert(po->mainloop != nullptr);
+
+ pa_context_state_t state;
+
+ if (po->context == nullptr && !pulse_output_setup_context(po, error_r))
+ return false;
+
+ while (true) {
+ state = pa_context_get_state(po->context);
+ switch (state) {
+ case PA_CONTEXT_READY:
+ /* nothing to do */
+ return true;
+
+ case PA_CONTEXT_UNCONNECTED:
+ case PA_CONTEXT_TERMINATED:
+ case PA_CONTEXT_FAILED:
+ /* failure */
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "failed to connect: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ pulse_output_delete_context(po);
+ return false;
+
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ /* wait some more */
+ pa_threaded_mainloop_wait(po->mainloop);
+ break;
+ }
+ }
+}
+
+#if PA_CHECK_VERSION(0,9,8)
+
+static void
+pulse_output_stream_suspended_cb(G_GNUC_UNUSED pa_stream *stream, void *userdata)
+{
+ PulseOutput *po = (PulseOutput *)userdata;
+
+ assert(stream == po->stream || po->stream == nullptr);
+ assert(po->mainloop != nullptr);
+
+ /* wake up the main loop to break out of the loop in
+ pulse_output_play() */
+ pa_threaded_mainloop_signal(po->mainloop, 0);
+}
+
+#endif
+
+static void
+pulse_output_stream_state_cb(pa_stream *stream, void *userdata)
+{
+ PulseOutput *po = (PulseOutput *)userdata;
+
+ assert(stream == po->stream || po->stream == nullptr);
+ assert(po->mainloop != nullptr);
+ assert(po->context != nullptr);
+
+ switch (pa_stream_get_state(stream)) {
+ case PA_STREAM_READY:
+ if (po->mixer != nullptr)
+ pulse_mixer_on_change(po->mixer, po->context, stream);
+
+ pa_threaded_mainloop_signal(po->mainloop, 0);
+ break;
+
+ case PA_STREAM_FAILED:
+ case PA_STREAM_TERMINATED:
+ if (po->mixer != nullptr)
+ pulse_mixer_on_disconnect(po->mixer);
+
+ pa_threaded_mainloop_signal(po->mainloop, 0);
+ break;
+
+ case PA_STREAM_UNCONNECTED:
+ case PA_STREAM_CREATING:
+ break;
+ }
+}
+
+static void
+pulse_output_stream_write_cb(G_GNUC_UNUSED pa_stream *stream, size_t nbytes,
+ void *userdata)
+{
+ PulseOutput *po = (PulseOutput *)userdata;
+
+ assert(po->mainloop != nullptr);
+
+ po->writable = nbytes;
+ pa_threaded_mainloop_signal(po->mainloop, 0);
+}
+
+/**
+ * Create, set up and connect a context.
+ *
+ * Caller must lock the main loop.
+ *
+ * @return true on success, false on error
+ */
+static bool
+pulse_output_setup_stream(PulseOutput *po, const pa_sample_spec *ss,
+ GError **error_r)
+{
+ assert(po != nullptr);
+ assert(po->context != nullptr);
+
+ po->stream = pa_stream_new(po->context, po->name, ss, nullptr);
+ if (po->stream == nullptr) {
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "pa_stream_new() has failed: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ return false;
+ }
+
+#if PA_CHECK_VERSION(0,9,8)
+ pa_stream_set_suspended_callback(po->stream,
+ pulse_output_stream_suspended_cb, po);
+#endif
+
+ pa_stream_set_state_callback(po->stream,
+ pulse_output_stream_state_cb, po);
+ pa_stream_set_write_callback(po->stream,
+ pulse_output_stream_write_cb, po);
+
+ return true;
+}
+
+static bool
+pulse_output_open(struct audio_output *ao, AudioFormat &audio_format,
+ GError **error_r)
+{
+ PulseOutput *po = (PulseOutput *)ao;
+ pa_sample_spec ss;
+ int error;
+
+ assert(po->mainloop != nullptr);
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ if (po->context != nullptr) {
+ switch (pa_context_get_state(po->context)) {
+ case PA_CONTEXT_UNCONNECTED:
+ case PA_CONTEXT_TERMINATED:
+ case PA_CONTEXT_FAILED:
+ /* the connection was closed meanwhile; delete
+ it, and pulse_output_wait_connection() will
+ reopen it */
+ pulse_output_delete_context(po);
+ break;
+
+ case PA_CONTEXT_READY:
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ break;
+ }
+ }
+
+ if (!pulse_output_wait_connection(po, error_r)) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return false;
+ }
+
+ /* MPD doesn't support the other pulseaudio sample formats, so
+ we just force MPD to send us everything as 16 bit */
+ audio_format.format = SampleFormat::S16;
+
+ ss.format = PA_SAMPLE_S16NE;
+ ss.rate = audio_format.sample_rate;
+ ss.channels = audio_format.channels;
+
+ /* create a stream .. */
+
+ if (!pulse_output_setup_stream(po, &ss, error_r)) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return false;
+ }
+
+ /* .. and connect it (asynchronously) */
+
+ error = pa_stream_connect_playback(po->stream, po->sink,
+ nullptr, pa_stream_flags_t(0),
+ nullptr, nullptr);
+ if (error < 0) {
+ pulse_output_delete_stream(po);
+
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "pa_stream_connect_playback() has failed: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return false;
+ }
+
+ pa_threaded_mainloop_unlock(po->mainloop);
+
+#if !PA_CHECK_VERSION(0,9,11)
+ po->pause = false;
+#endif
+
+ return true;
+}
+
+static void
+pulse_output_close(struct audio_output *ao)
+{
+ PulseOutput *po = (PulseOutput *)ao;
+ pa_operation *o;
+
+ assert(po->mainloop != nullptr);
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ if (pa_stream_get_state(po->stream) == PA_STREAM_READY) {
+ o = pa_stream_drain(po->stream,
+ pulse_output_stream_success_cb, po);
+ if (o == nullptr) {
+ g_warning("pa_stream_drain() has failed: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ } else
+ pulse_wait_for_operation(po->mainloop, o);
+ }
+
+ pulse_output_delete_stream(po);
+
+ if (po->context != nullptr &&
+ pa_context_get_state(po->context) != PA_CONTEXT_READY)
+ pulse_output_delete_context(po);
+
+ pa_threaded_mainloop_unlock(po->mainloop);
+}
+
+/**
+ * Check if the stream is (already) connected, and waits if not. The
+ * mainloop must be locked before calling this function.
+ *
+ * @return true on success, false on error
+ */
+static bool
+pulse_output_wait_stream(PulseOutput *po, GError **error_r)
+{
+ while (true) {
+ switch (pa_stream_get_state(po->stream)) {
+ case PA_STREAM_READY:
+ return true;
+
+ case PA_STREAM_FAILED:
+ case PA_STREAM_TERMINATED:
+ case PA_STREAM_UNCONNECTED:
+ g_set_error(error_r, pulse_output_quark(),
+ pa_context_errno(po->context),
+ "failed to connect the stream: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ return false;
+
+ case PA_STREAM_CREATING:
+ pa_threaded_mainloop_wait(po->mainloop);
+ break;
+ }
+ }
+}
+
+/**
+ * Determines whether the stream is paused. On libpulse older than
+ * 0.9.11, it uses a custom pause flag.
+ */
+static bool
+pulse_output_stream_is_paused(PulseOutput *po)
+{
+ assert(po->stream != nullptr);
+
+#if !defined(PA_CHECK_VERSION) || !PA_CHECK_VERSION(0,9,11)
+ return po->pause;
+#else
+ return pa_stream_is_corked(po->stream);
+#endif
+}
+
+/**
+ * Sets cork mode on the stream.
+ */
+static bool
+pulse_output_stream_pause(PulseOutput *po, bool pause,
+ GError **error_r)
+{
+ pa_operation *o;
+
+ assert(po->mainloop != nullptr);
+ assert(po->context != nullptr);
+ assert(po->stream != nullptr);
+
+ o = pa_stream_cork(po->stream, pause,
+ pulse_output_stream_success_cb, po);
+ if (o == nullptr) {
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "pa_stream_cork() has failed: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ return false;
+ }
+
+ if (!pulse_wait_for_operation(po->mainloop, o)) {
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "pa_stream_cork() has failed: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ return false;
+ }
+
+#if !PA_CHECK_VERSION(0,9,11)
+ po->pause = pause;
+#endif
+ return true;
+}
+
+static unsigned
+pulse_output_delay(struct audio_output *ao)
+{
+ PulseOutput *po = (PulseOutput *)ao;
+ unsigned result = 0;
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ if (po->base.pause && pulse_output_stream_is_paused(po) &&
+ pa_stream_get_state(po->stream) == PA_STREAM_READY)
+ /* idle while paused */
+ result = 1000;
+
+ pa_threaded_mainloop_unlock(po->mainloop);
+
+ return result;
+}
+
+static size_t
+pulse_output_play(struct audio_output *ao, const void *chunk, size_t size,
+ GError **error_r)
+{
+ PulseOutput *po = (PulseOutput *)ao;
+ int error;
+
+ assert(po->mainloop != nullptr);
+ assert(po->stream != nullptr);
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ /* check if the stream is (already) connected */
+
+ if (!pulse_output_wait_stream(po, error_r)) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return 0;
+ }
+
+ assert(po->context != nullptr);
+
+ /* unpause if previously paused */
+
+ if (pulse_output_stream_is_paused(po) &&
+ !pulse_output_stream_pause(po, false, error_r)) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return 0;
+ }
+
+ /* wait until the server allows us to write */
+
+ while (po->writable == 0) {
+#if PA_CHECK_VERSION(0,9,8)
+ if (pa_stream_is_suspended(po->stream)) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "suspended");
+ return 0;
+ }
+#endif
+
+ pa_threaded_mainloop_wait(po->mainloop);
+
+ if (pa_stream_get_state(po->stream) != PA_STREAM_READY) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "disconnected");
+ return 0;
+ }
+ }
+
+ /* now write */
+
+ if (size > po->writable)
+ /* don't send more than possible */
+ size = po->writable;
+
+ po->writable -= size;
+
+ error = pa_stream_write(po->stream, chunk, size, nullptr,
+ 0, PA_SEEK_RELATIVE);
+ pa_threaded_mainloop_unlock(po->mainloop);
+ if (error < 0) {
+ g_set_error(error_r, pulse_output_quark(), error,
+ "%s", pa_strerror(error));
+ return 0;
+ }
+
+ return size;
+}
+
+static void
+pulse_output_cancel(struct audio_output *ao)
+{
+ PulseOutput *po = (PulseOutput *)ao;
+ pa_operation *o;
+
+ assert(po->mainloop != nullptr);
+ assert(po->stream != nullptr);
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ if (pa_stream_get_state(po->stream) != PA_STREAM_READY) {
+ /* no need to flush when the stream isn't connected
+ yet */
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return;
+ }
+
+ assert(po->context != nullptr);
+
+ o = pa_stream_flush(po->stream, pulse_output_stream_success_cb, po);
+ if (o == nullptr) {
+ g_warning("pa_stream_flush() has failed: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return;
+ }
+
+ pulse_wait_for_operation(po->mainloop, o);
+ pa_threaded_mainloop_unlock(po->mainloop);
+}
+
+static bool
+pulse_output_pause(struct audio_output *ao)
+{
+ PulseOutput *po = (PulseOutput *)ao;
+ GError *error = nullptr;
+
+ assert(po->mainloop != nullptr);
+ assert(po->stream != nullptr);
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ /* check if the stream is (already/still) connected */
+
+ if (!pulse_output_wait_stream(po, &error)) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return false;
+ }
+
+ assert(po->context != nullptr);
+
+ /* cork the stream */
+
+ if (!pulse_output_stream_is_paused(po) &&
+ !pulse_output_stream_pause(po, true, &error)) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return false;
+ }
+
+ pa_threaded_mainloop_unlock(po->mainloop);
+
+ return true;
+}
+
+static bool
+pulse_output_test_default_device(void)
+{
+ bool success;
+
+ const config_param empty;
+ PulseOutput *po = (PulseOutput *)pulse_output_init(empty, nullptr);
+ if (po == nullptr)
+ return false;
+
+ success = pulse_output_wait_connection(po, nullptr);
+ pulse_output_finish(&po->base);
+
+ return success;
+}
+
+const struct audio_output_plugin pulse_output_plugin = {
+ "pulse",
+ pulse_output_test_default_device,
+ pulse_output_init,
+ pulse_output_finish,
+ pulse_output_enable,
+ pulse_output_disable,
+ pulse_output_open,
+ pulse_output_close,
+ pulse_output_delay,
+ nullptr,
+ pulse_output_play,
+ nullptr,
+ pulse_output_cancel,
+ pulse_output_pause,
+
+ &pulse_mixer_plugin,
+};
diff --git a/src/output/PulseOutputPlugin.hxx b/src/output/PulseOutputPlugin.hxx
new file mode 100644
index 000000000..58dc6703c
--- /dev/null
+++ b/src/output/PulseOutputPlugin.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_PULSE_OUTPUT_PLUGIN_HXX
+#define MPD_PULSE_OUTPUT_PLUGIN_HXX
+
+#include "gerror.h"
+
+struct PulseOutput;
+struct PulseMixer;
+struct pa_cvolume;
+
+extern const struct audio_output_plugin pulse_output_plugin;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void
+pulse_output_lock(PulseOutput *po);
+
+void
+pulse_output_unlock(PulseOutput *po);
+
+void
+pulse_output_set_mixer(PulseOutput *po, PulseMixer *pm);
+
+void
+pulse_output_clear_mixer(PulseOutput *po, PulseMixer *pm);
+
+bool
+pulse_output_set_volume(PulseOutput *po,
+ const struct pa_cvolume *volume, GError **error_r);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/output/RecorderOutputPlugin.cxx b/src/output/RecorderOutputPlugin.cxx
new file mode 100644
index 000000000..afae17e84
--- /dev/null
+++ b/src/output/RecorderOutputPlugin.cxx
@@ -0,0 +1,274 @@
+/*
+ * 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 "RecorderOutputPlugin.hxx"
+#include "OutputAPI.hxx"
+#include "EncoderPlugin.hxx"
+#include "EncoderList.hxx"
+#include "fd_util.h"
+#include "open.h"
+
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "recorder"
+
+struct RecorderOutput {
+ struct audio_output base;
+
+ /**
+ * The configured encoder plugin.
+ */
+ Encoder *encoder;
+
+ /**
+ * The destination file name.
+ */
+ const char *path;
+
+ /**
+ * The destination file descriptor.
+ */
+ int fd;
+
+ /**
+ * The buffer for encoder_read().
+ */
+ char buffer[32768];
+
+ bool Initialize(const config_param &param, GError **error_r) {
+ return ao_base_init(&base, &recorder_output_plugin, param,
+ error_r);
+ }
+
+ void Deinitialize() {
+ ao_base_finish(&base);
+ }
+
+ bool Configure(const config_param &param, GError **error_r);
+
+ bool WriteToFile(const void *data, size_t length, GError **error_r);
+
+ /**
+ * Writes pending data from the encoder to the output file.
+ */
+ bool EncoderToFile(GError **error_r);
+};
+
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+recorder_output_quark(void)
+{
+ return g_quark_from_static_string("recorder_output");
+}
+
+inline bool
+RecorderOutput::Configure(const config_param &param, GError **error_r)
+{
+ /* read configuration */
+
+ const char *encoder_name =
+ param.GetBlockValue("encoder", "vorbis");
+ const auto encoder_plugin = encoder_plugin_get(encoder_name);
+ if (encoder_plugin == nullptr) {
+ g_set_error(error_r, recorder_output_quark(), 0,
+ "No such encoder: %s", encoder_name);
+ return false;
+ }
+
+ path = param.GetBlockValue("path");
+ if (path == nullptr) {
+ g_set_error(error_r, recorder_output_quark(), 0,
+ "'path' not configured");
+ return false;
+ }
+
+ /* initialize encoder */
+
+ encoder = encoder_init(*encoder_plugin, param, error_r);
+ if (encoder == nullptr)
+ return false;
+
+ return true;
+}
+
+static audio_output *
+recorder_output_init(const config_param &param, GError **error_r)
+{
+ RecorderOutput *recorder = new RecorderOutput();
+
+ if (!recorder->Initialize(param, error_r)) {
+ delete recorder;
+ return nullptr;
+ }
+
+ if (!recorder->Configure(param, error_r)) {
+ recorder->Deinitialize();
+ delete recorder;
+ return nullptr;
+ }
+
+ return &recorder->base;
+}
+
+static void
+recorder_output_finish(struct audio_output *ao)
+{
+ RecorderOutput *recorder = (RecorderOutput *)ao;
+
+ encoder_finish(recorder->encoder);
+ recorder->Deinitialize();
+ delete recorder;
+}
+
+inline bool
+RecorderOutput::WriteToFile(const void *_data, size_t length, GError **error_r)
+{
+ assert(length > 0);
+
+ const uint8_t *data = (const uint8_t *)_data, *end = data + length;
+
+ while (true) {
+ ssize_t nbytes = write(fd, data, end - data);
+ if (nbytes > 0) {
+ data += nbytes;
+ if (data == end)
+ return true;
+ } else if (nbytes == 0) {
+ /* shouldn't happen for files */
+ g_set_error(error_r, recorder_output_quark(), 0,
+ "write() returned 0");
+ return false;
+ } else if (errno != EINTR) {
+ g_set_error(error_r, recorder_output_quark(), 0,
+ "Failed to write to '%s': %s",
+ path, g_strerror(errno));
+ return false;
+ }
+ }
+}
+
+inline bool
+RecorderOutput::EncoderToFile(GError **error_r)
+{
+ assert(fd >= 0);
+
+ while (true) {
+ /* read from the encoder */
+
+ size_t size = encoder_read(encoder, buffer, sizeof(buffer));
+ if (size == 0)
+ return true;
+
+ /* write everything into the file */
+
+ if (!WriteToFile(buffer, size, error_r))
+ return false;
+ }
+}
+
+static bool
+recorder_output_open(struct audio_output *ao,
+ AudioFormat &audio_format,
+ GError **error_r)
+{
+ RecorderOutput *recorder = (RecorderOutput *)ao;
+
+ /* create the output file */
+
+ recorder->fd = open_cloexec(recorder->path,
+ O_CREAT|O_WRONLY|O_TRUNC|O_BINARY,
+ 0666);
+ if (recorder->fd < 0) {
+ g_set_error(error_r, recorder_output_quark(), 0,
+ "Failed to create '%s': %s",
+ recorder->path, g_strerror(errno));
+ return false;
+ }
+
+ /* open the encoder */
+
+ if (!encoder_open(recorder->encoder, audio_format, error_r)) {
+ close(recorder->fd);
+ unlink(recorder->path);
+ return false;
+ }
+
+ if (!recorder->EncoderToFile(error_r)) {
+ encoder_close(recorder->encoder);
+ close(recorder->fd);
+ unlink(recorder->path);
+ return false;
+ }
+
+ return true;
+}
+
+static void
+recorder_output_close(struct audio_output *ao)
+{
+ RecorderOutput *recorder = (RecorderOutput *)ao;
+
+ /* flush the encoder and write the rest to the file */
+
+ if (encoder_end(recorder->encoder, nullptr))
+ recorder->EncoderToFile(nullptr);
+
+ /* now really close everything */
+
+ encoder_close(recorder->encoder);
+
+ close(recorder->fd);
+}
+
+static size_t
+recorder_output_play(struct audio_output *ao, const void *chunk, size_t size,
+ GError **error_r)
+{
+ RecorderOutput *recorder = (RecorderOutput *)ao;
+
+ return encoder_write(recorder->encoder, chunk, size, error_r) &&
+ recorder->EncoderToFile(error_r)
+ ? size : 0;
+}
+
+const struct audio_output_plugin recorder_output_plugin = {
+ "recorder",
+ nullptr,
+ recorder_output_init,
+ recorder_output_finish,
+ nullptr,
+ nullptr,
+ recorder_output_open,
+ recorder_output_close,
+ nullptr,
+ nullptr,
+ recorder_output_play,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+};
diff --git a/src/output/RecorderOutputPlugin.hxx b/src/output/RecorderOutputPlugin.hxx
new file mode 100644
index 000000000..a27f51e23
--- /dev/null
+++ b/src/output/RecorderOutputPlugin.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_RECORDER_OUTPUT_PLUGIN_HXX
+#define MPD_RECORDER_OUTPUT_PLUGIN_HXX
+
+extern const struct audio_output_plugin recorder_output_plugin;
+
+#endif
diff --git a/src/output/RoarOutputPlugin.cxx b/src/output/RoarOutputPlugin.cxx
new file mode 100644
index 000000000..36f7c395b
--- /dev/null
+++ b/src/output/RoarOutputPlugin.cxx
@@ -0,0 +1,399 @@
+/*
+ * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2010-2011 Philipp 'ph3-der-loewe' Schafft
+ * Copyright (C) 2010-2011 Hans-Kristian 'maister' Arntzen
+ *
+ * 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 "RoarOutputPlugin.hxx"
+#include "OutputAPI.hxx"
+#include "MixerList.hxx"
+#include "thread/Mutex.hxx"
+
+#include <glib.h>
+
+/* libroar/services.h declares roar_service_stream::new - work around
+ this C++ problem */
+#define new _new
+#include <roaraudio.h>
+#undef new
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "roaraudio"
+
+struct RoarOutput {
+ struct audio_output base;
+
+ roar_vs_t * vss;
+ int err;
+ char *host;
+ char *name;
+ int role;
+ struct roar_connection con;
+ struct roar_audio_info info;
+ Mutex mutex;
+ volatile bool alive;
+
+ RoarOutput()
+ :err(ROAR_ERROR_NONE),
+ host(nullptr), name(nullptr) {}
+
+ ~RoarOutput() {
+ g_free(host);
+ g_free(name);
+ }
+};
+
+static inline GQuark
+roar_output_quark(void)
+{
+ return g_quark_from_static_string("roar_output");
+}
+
+static int
+roar_output_get_volume_locked(RoarOutput *roar)
+{
+ if (roar->vss == nullptr || !roar->alive)
+ return -1;
+
+ float l, r;
+ int error;
+ if (roar_vs_volume_get(roar->vss, &l, &r, &error) < 0)
+ return -1;
+
+ return (l + r) * 50;
+}
+
+int
+roar_output_get_volume(RoarOutput *roar)
+{
+ const ScopeLock protect(roar->mutex);
+ return roar_output_get_volume_locked(roar);
+}
+
+static bool
+roar_output_set_volume_locked(RoarOutput *roar, unsigned volume)
+{
+ assert(volume <= 100);
+
+ if (roar->vss == nullptr || !roar->alive)
+ return false;
+
+ int error;
+ float level = volume / 100.0;
+
+ roar_vs_volume_mono(roar->vss, level, &error);
+ return true;
+}
+
+bool
+roar_output_set_volume(RoarOutput *roar, unsigned volume)
+{
+ const ScopeLock protect(roar->mutex);
+ return roar_output_set_volume_locked(roar, volume);
+}
+
+static void
+roar_configure(RoarOutput *self, const config_param &param)
+{
+ self->host = param.DupBlockString("server", nullptr);
+ self->name = param.DupBlockString("name", "MPD");
+
+ const char *role = param.GetBlockValue("role", "music");
+ self->role = role != nullptr
+ ? roar_str2role(role)
+ : ROAR_ROLE_MUSIC;
+}
+
+static struct audio_output *
+roar_init(const config_param &param, GError **error_r)
+{
+ RoarOutput *self = new RoarOutput();
+
+ if (!ao_base_init(&self->base, &roar_output_plugin, param, error_r)) {
+ delete self;
+ return nullptr;
+ }
+
+ roar_configure(self, param);
+ return &self->base;
+}
+
+static void
+roar_finish(struct audio_output *ao)
+{
+ RoarOutput *self = (RoarOutput *)ao;
+
+ ao_base_finish(&self->base);
+ delete self;
+}
+
+static void
+roar_use_audio_format(struct roar_audio_info *info,
+ AudioFormat &audio_format)
+{
+ info->rate = audio_format.sample_rate;
+ info->channels = audio_format.channels;
+ info->codec = ROAR_CODEC_PCM_S;
+
+ switch (audio_format.format) {
+ case SampleFormat::UNDEFINED:
+ case SampleFormat::FLOAT:
+ case SampleFormat::DSD:
+ info->bits = 16;
+ audio_format.format = SampleFormat::S16;
+ break;
+
+ case SampleFormat::S8:
+ info->bits = 8;
+ break;
+
+ case SampleFormat::S16:
+ info->bits = 16;
+ break;
+
+ case SampleFormat::S24_P32:
+ info->bits = 32;
+ audio_format.format = SampleFormat::S32;
+ break;
+
+ case SampleFormat::S32:
+ info->bits = 32;
+ break;
+ }
+}
+
+static bool
+roar_open(struct audio_output *ao, AudioFormat &audio_format, GError **error)
+{
+ RoarOutput *self = (RoarOutput *)ao;
+ const ScopeLock protect(self->mutex);
+
+ if (roar_simple_connect(&(self->con), self->host, self->name) < 0)
+ {
+ g_set_error(error, roar_output_quark(), 0,
+ "Failed to connect to Roar server");
+ return false;
+ }
+
+ self->vss = roar_vs_new_from_con(&(self->con), &(self->err));
+
+ if (self->vss == nullptr || self->err != ROAR_ERROR_NONE)
+ {
+ g_set_error(error, roar_output_quark(), 0,
+ "Failed to connect to server");
+ return false;
+ }
+
+ roar_use_audio_format(&self->info, audio_format);
+
+ if (roar_vs_stream(self->vss, &(self->info), ROAR_DIR_PLAY,
+ &(self->err)) < 0)
+ {
+ g_set_error(error, roar_output_quark(), 0, "Failed to start stream");
+ return false;
+ }
+ roar_vs_role(self->vss, self->role, &(self->err));
+ self->alive = true;
+
+ return true;
+}
+
+static void
+roar_close(struct audio_output *ao)
+{
+ RoarOutput *self = (RoarOutput *)ao;
+ const ScopeLock protect(self->mutex);
+
+ self->alive = false;
+
+ if (self->vss != nullptr)
+ roar_vs_close(self->vss, ROAR_VS_TRUE, &(self->err));
+ self->vss = nullptr;
+ roar_disconnect(&(self->con));
+}
+
+static void
+roar_cancel_locked(RoarOutput *self)
+{
+ if (self->vss == nullptr)
+ return;
+
+ roar_vs_t *vss = self->vss;
+ self->vss = nullptr;
+ roar_vs_close(vss, ROAR_VS_TRUE, &(self->err));
+ self->alive = false;
+
+ vss = roar_vs_new_from_con(&(self->con), &(self->err));
+ if (vss == nullptr)
+ return;
+
+ if (roar_vs_stream(vss, &(self->info), ROAR_DIR_PLAY,
+ &(self->err)) < 0) {
+ roar_vs_close(vss, ROAR_VS_TRUE, &(self->err));
+ g_warning("Failed to start stream");
+ return;
+ }
+
+ roar_vs_role(vss, self->role, &(self->err));
+ self->vss = vss;
+ self->alive = true;
+}
+
+static void
+roar_cancel(struct audio_output *ao)
+{
+ RoarOutput *self = (RoarOutput *)ao;
+
+ const ScopeLock protect(self->mutex);
+ roar_cancel_locked(self);
+}
+
+static size_t
+roar_play(struct audio_output *ao, const void *chunk, size_t size, GError **error)
+{
+ RoarOutput *self = (RoarOutput *)ao;
+ ssize_t rc;
+
+ if (self->vss == nullptr)
+ {
+ g_set_error(error, roar_output_quark(), 0, "Connection is invalid");
+ return 0;
+ }
+
+ rc = roar_vs_write(self->vss, chunk, size, &(self->err));
+ if ( rc <= 0 )
+ {
+ g_set_error(error, roar_output_quark(), 0, "Failed to play data");
+ return 0;
+ }
+
+ return rc;
+}
+
+static const char*
+roar_tag_convert(enum tag_type type, bool *is_uuid)
+{
+ *is_uuid = false;
+ switch (type)
+ {
+ case TAG_ARTIST:
+ case TAG_ALBUM_ARTIST:
+ return "AUTHOR";
+ case TAG_ALBUM:
+ return "ALBUM";
+ case TAG_TITLE:
+ return "TITLE";
+ case TAG_TRACK:
+ return "TRACK";
+ case TAG_NAME:
+ return "NAME";
+ case TAG_GENRE:
+ return "GENRE";
+ case TAG_DATE:
+ return "DATE";
+ case TAG_PERFORMER:
+ return "PERFORMER";
+ case TAG_COMMENT:
+ return "COMMENT";
+ case TAG_DISC:
+ return "DISCID";
+ case TAG_COMPOSER:
+#ifdef ROAR_META_TYPE_COMPOSER
+ return "COMPOSER";
+#else
+ return "AUTHOR";
+#endif
+ case TAG_MUSICBRAINZ_ARTISTID:
+ case TAG_MUSICBRAINZ_ALBUMID:
+ case TAG_MUSICBRAINZ_ALBUMARTISTID:
+ case TAG_MUSICBRAINZ_TRACKID:
+ *is_uuid = true;
+ return "HASH";
+
+ default:
+ return nullptr;
+ }
+}
+
+static void
+roar_send_tag(struct audio_output *ao, const Tag *meta)
+{
+ RoarOutput *self = (RoarOutput *)ao;
+
+ if (self->vss == nullptr)
+ return;
+
+ const ScopeLock protect(self->mutex);
+
+ size_t cnt = 1;
+ struct roar_keyval vals[32];
+ memset(vals, 0, sizeof(vals));
+ char uuid_buf[32][64];
+
+ char timebuf[16];
+ snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d",
+ meta->time / 3600, (meta->time % 3600) / 60, meta->time % 60);
+
+ vals[0].key = g_strdup("LENGTH");
+ vals[0].value = timebuf;
+
+ for (unsigned i = 0; i < meta->num_items && cnt < 32; i++)
+ {
+ bool is_uuid = false;
+ const char *key = roar_tag_convert(meta->items[i]->type, &is_uuid);
+ if (key != nullptr)
+ {
+ if (is_uuid)
+ {
+ snprintf(uuid_buf[cnt], sizeof(uuid_buf[0]), "{UUID}%s",
+ meta->items[i]->value);
+ vals[cnt].key = g_strdup(key);
+ vals[cnt].value = uuid_buf[cnt];
+ }
+ else
+ {
+ vals[cnt].key = g_strdup(key);
+ vals[cnt].value = meta->items[i]->value;
+ }
+ cnt++;
+ }
+ }
+
+ roar_vs_meta(self->vss, vals, cnt, &(self->err));
+
+ for (unsigned i = 0; i < 32; i++)
+ g_free(vals[i].key);
+}
+
+const struct audio_output_plugin roar_output_plugin = {
+ "roar",
+ nullptr,
+ roar_init,
+ roar_finish,
+ nullptr,
+ nullptr,
+ roar_open,
+ roar_close,
+ nullptr,
+ roar_send_tag,
+ roar_play,
+ nullptr,
+ roar_cancel,
+ nullptr,
+ &roar_mixer_plugin,
+};
diff --git a/src/output/RoarOutputPlugin.hxx b/src/output/RoarOutputPlugin.hxx
new file mode 100644
index 000000000..faa4b4d5c
--- /dev/null
+++ b/src/output/RoarOutputPlugin.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_ROAR_OUTPUT_PLUGIN_H
+#define MPD_ROAR_OUTPUT_PLUGIN_H
+
+struct RoarOutput;
+
+extern const struct audio_output_plugin roar_output_plugin;
+
+int
+roar_output_get_volume(RoarOutput *roar);
+
+bool
+roar_output_set_volume(RoarOutput *roar, unsigned volume);
+
+#endif
diff --git a/src/output/ShoutOutputPlugin.cxx b/src/output/ShoutOutputPlugin.cxx
new file mode 100644
index 000000000..2d2c0afd0
--- /dev/null
+++ b/src/output/ShoutOutputPlugin.cxx
@@ -0,0 +1,560 @@
+/*
+ * 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 "ShoutOutputPlugin.hxx"
+#include "OutputAPI.hxx"
+#include "EncoderPlugin.hxx"
+#include "EncoderList.hxx"
+#include "mpd_error.h"
+
+#include <shout/shout.h>
+#include <glib.h>
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "shout"
+
+static constexpr unsigned DEFAULT_CONN_TIMEOUT = 2;
+
+struct ShoutOutput final {
+ struct audio_output base;
+
+ shout_t *shout_conn;
+ shout_metadata_t *shout_meta;
+
+ Encoder *encoder;
+
+ float quality;
+ int bitrate;
+
+ int timeout;
+
+ uint8_t buffer[32768];
+
+ ShoutOutput()
+ :shout_conn(shout_new()),
+ shout_meta(shout_metadata_new()),
+ quality(-2.0),
+ bitrate(-1),
+ timeout(DEFAULT_CONN_TIMEOUT) {}
+
+ ~ShoutOutput() {
+ if (shout_meta != nullptr)
+ shout_metadata_free(shout_meta);
+ if (shout_conn != nullptr)
+ shout_free(shout_conn);
+ }
+
+ bool Initialize(const config_param &param, GError **error_r) {
+ return ao_base_init(&base, &shout_output_plugin, param,
+ error_r);
+ }
+
+ void Deinitialize() {
+ ao_base_finish(&base);
+ }
+
+ bool Configure(const config_param &param, GError **error_r);
+};
+
+static int shout_init_count;
+
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+shout_output_quark(void)
+{
+ return g_quark_from_static_string("shout_output");
+}
+
+static const EncoderPlugin *
+shout_encoder_plugin_get(const char *name)
+{
+ if (strcmp(name, "ogg") == 0)
+ name = "vorbis";
+ else if (strcmp(name, "mp3") == 0)
+ name = "lame";
+
+ return encoder_plugin_get(name);
+}
+
+gcc_pure
+static const char *
+require_block_string(const config_param &param, const char *name)
+{
+ const char *value = param.GetBlockValue(name);
+ if (value == nullptr)
+ MPD_ERROR("no \"%s\" defined for shout device defined at line " \
+ "%i\n", name, param.line);
+
+ return value;
+}
+
+inline bool
+ShoutOutput::Configure(const config_param &param, GError **error_r)
+{
+
+ const AudioFormat audio_format = base.config_audio_format;
+ if (!audio_format.IsFullyDefined()) {
+ g_set_error(error_r, shout_output_quark(), 0,
+ "Need full audio format specification");
+ return nullptr;
+ }
+
+ const char *host = require_block_string(param, "host");
+ const char *mount = require_block_string(param, "mount");
+ unsigned port = param.GetBlockValue("port", 0u);
+ if (port == 0) {
+ g_set_error(error_r, shout_output_quark(), 0,
+ "shout port must be configured");
+ return false;
+ }
+
+ const char *passwd = require_block_string(param, "password");
+ const char *name = require_block_string(param, "name");
+
+ bool is_public = param.GetBlockValue("public", false);
+
+ const char *user = param.GetBlockValue("user", "source");
+
+ const char *value = param.GetBlockValue("quality");
+ if (value != nullptr) {
+ char *test;
+ quality = strtod(value, &test);
+
+ if (*test != '\0' || quality < -1.0 || quality > 10.0) {
+ g_set_error(error_r, shout_output_quark(), 0,
+ "shout quality \"%s\" is not a number in the "
+ "range -1 to 10, line %i",
+ value, param.line);
+ return false;
+ }
+
+ if (param.GetBlockValue("bitrate") != nullptr) {
+ g_set_error(error_r, shout_output_quark(), 0,
+ "quality and bitrate are "
+ "both defined");
+ return false;
+ }
+ } else {
+ value = param.GetBlockValue("bitrate");
+ if (value == nullptr) {
+ g_set_error(error_r, shout_output_quark(), 0,
+ "neither bitrate nor quality defined");
+ return false;
+ }
+
+ char *test;
+ bitrate = strtol(value, &test, 10);
+
+ if (*test != '\0' || bitrate <= 0) {
+ g_set_error(error_r, shout_output_quark(), 0,
+ "bitrate must be a positive integer");
+ return false;
+ }
+ }
+
+ const char *encoding = param.GetBlockValue("encoding", "ogg");
+ const auto encoder_plugin = shout_encoder_plugin_get(encoding);
+ if (encoder_plugin == nullptr) {
+ g_set_error(error_r, shout_output_quark(), 0,
+ "couldn't find shout encoder plugin \"%s\"",
+ encoding);
+ return false;
+ }
+
+ encoder = encoder_init(*encoder_plugin, param, error_r);
+ if (encoder == nullptr)
+ return false;
+
+ unsigned shout_format;
+ if (strcmp(encoding, "mp3") == 0 || strcmp(encoding, "lame") == 0)
+ shout_format = SHOUT_FORMAT_MP3;
+ else
+ shout_format = SHOUT_FORMAT_OGG;
+
+ unsigned protocol;
+ value = param.GetBlockValue("protocol");
+ if (value != nullptr) {
+ if (0 == strcmp(value, "shoutcast") &&
+ 0 != strcmp(encoding, "mp3")) {
+ g_set_error(error_r, shout_output_quark(), 0,
+ "you cannot stream \"%s\" to shoutcast, use mp3",
+ encoding);
+ return false;
+ } else if (0 == strcmp(value, "shoutcast"))
+ protocol = SHOUT_PROTOCOL_ICY;
+ else if (0 == strcmp(value, "icecast1"))
+ protocol = SHOUT_PROTOCOL_XAUDIOCAST;
+ else if (0 == strcmp(value, "icecast2"))
+ protocol = SHOUT_PROTOCOL_HTTP;
+ else {
+ g_set_error(error_r, shout_output_quark(), 0,
+ "shout protocol \"%s\" is not \"shoutcast\" or "
+ "\"icecast1\"or \"icecast2\"",
+ value);
+ return false;
+ }
+ } else {
+ protocol = SHOUT_PROTOCOL_HTTP;
+ }
+
+ if (shout_set_host(shout_conn, host) != SHOUTERR_SUCCESS ||
+ shout_set_port(shout_conn, port) != SHOUTERR_SUCCESS ||
+ shout_set_password(shout_conn, passwd) != SHOUTERR_SUCCESS ||
+ shout_set_mount(shout_conn, mount) != SHOUTERR_SUCCESS ||
+ shout_set_name(shout_conn, name) != SHOUTERR_SUCCESS ||
+ shout_set_user(shout_conn, user) != SHOUTERR_SUCCESS ||
+ shout_set_public(shout_conn, is_public) != SHOUTERR_SUCCESS ||
+ shout_set_format(shout_conn, shout_format)
+ != SHOUTERR_SUCCESS ||
+ shout_set_protocol(shout_conn, protocol) != SHOUTERR_SUCCESS ||
+ shout_set_agent(shout_conn, "MPD") != SHOUTERR_SUCCESS) {
+ g_set_error(error_r, shout_output_quark(), 0,
+ "%s", shout_get_error(shout_conn));
+ return false;
+ }
+
+ /* optional paramters */
+ timeout = param.GetBlockValue("timeout", DEFAULT_CONN_TIMEOUT);
+
+ value = param.GetBlockValue("genre");
+ if (value != nullptr && shout_set_genre(shout_conn, value)) {
+ g_set_error(error_r, shout_output_quark(), 0,
+ "%s", shout_get_error(shout_conn));
+ return false;
+ }
+
+ value = param.GetBlockValue("description");
+ if (value != nullptr && shout_set_description(shout_conn, value)) {
+ g_set_error(error_r, shout_output_quark(), 0,
+ "%s", shout_get_error(shout_conn));
+ return false;
+ }
+
+ value = param.GetBlockValue("url");
+ if (value != nullptr && shout_set_url(shout_conn, value)) {
+ g_set_error(error_r, shout_output_quark(), 0,
+ "%s", shout_get_error(shout_conn));
+ return false;
+ }
+
+ {
+ char temp[11];
+ memset(temp, 0, sizeof(temp));
+
+ snprintf(temp, sizeof(temp), "%u", audio_format.channels);
+ shout_set_audio_info(shout_conn, SHOUT_AI_CHANNELS, temp);
+
+ snprintf(temp, sizeof(temp), "%u", audio_format.sample_rate);
+
+ shout_set_audio_info(shout_conn, SHOUT_AI_SAMPLERATE, temp);
+
+ if (quality >= -1.0) {
+ snprintf(temp, sizeof(temp), "%2.2f", quality);
+ shout_set_audio_info(shout_conn, SHOUT_AI_QUALITY,
+ temp);
+ } else {
+ snprintf(temp, sizeof(temp), "%d", bitrate);
+ shout_set_audio_info(shout_conn, SHOUT_AI_BITRATE,
+ temp);
+ }
+ }
+
+ return true;
+}
+
+static struct audio_output *
+my_shout_init_driver(const config_param &param, GError **error_r)
+{
+ ShoutOutput *sd = new ShoutOutput();
+ if (!sd->Initialize(param, error_r)) {
+ delete sd;
+ return nullptr;
+ }
+
+ if (!sd->Configure(param, error_r)) {
+ sd->Deinitialize();
+ delete sd;
+ return nullptr;
+ }
+
+ if (shout_init_count == 0)
+ shout_init();
+
+ shout_init_count++;
+
+ return &sd->base;
+}
+
+static bool
+handle_shout_error(ShoutOutput *sd, int err, GError **error)
+{
+ switch (err) {
+ case SHOUTERR_SUCCESS:
+ break;
+
+ case SHOUTERR_UNCONNECTED:
+ case SHOUTERR_SOCKET:
+ g_set_error(error, shout_output_quark(), err,
+ "Lost shout connection to %s:%i: %s",
+ shout_get_host(sd->shout_conn),
+ shout_get_port(sd->shout_conn),
+ shout_get_error(sd->shout_conn));
+ return false;
+
+ default:
+ g_set_error(error, shout_output_quark(), err,
+ "connection to %s:%i error: %s",
+ shout_get_host(sd->shout_conn),
+ shout_get_port(sd->shout_conn),
+ shout_get_error(sd->shout_conn));
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+write_page(ShoutOutput *sd, GError **error)
+{
+ assert(sd->encoder != nullptr);
+
+ while (true) {
+ size_t nbytes = encoder_read(sd->encoder,
+ sd->buffer, sizeof(sd->buffer));
+ if (nbytes == 0)
+ return true;
+
+ int err = shout_send(sd->shout_conn, sd->buffer, nbytes);
+ if (!handle_shout_error(sd, err, error))
+ return false;
+ }
+
+ return true;
+}
+
+static void close_shout_conn(ShoutOutput * sd)
+{
+ if (sd->encoder != nullptr) {
+ if (encoder_end(sd->encoder, nullptr))
+ write_page(sd, nullptr);
+
+ encoder_close(sd->encoder);
+ }
+
+ if (shout_get_connected(sd->shout_conn) != SHOUTERR_UNCONNECTED &&
+ shout_close(sd->shout_conn) != SHOUTERR_SUCCESS) {
+ g_warning("problem closing connection to shout server: %s\n",
+ shout_get_error(sd->shout_conn));
+ }
+}
+
+static void
+my_shout_finish_driver(struct audio_output *ao)
+{
+ ShoutOutput *sd = (ShoutOutput *)ao;
+
+ encoder_finish(sd->encoder);
+
+ sd->Deinitialize();
+ delete sd;
+
+ shout_init_count--;
+
+ if (shout_init_count == 0)
+ shout_shutdown();
+}
+
+static void
+my_shout_drop_buffered_audio(struct audio_output *ao)
+{
+ G_GNUC_UNUSED
+ ShoutOutput *sd = (ShoutOutput *)ao;
+
+ /* needs to be implemented for shout */
+}
+
+static void
+my_shout_close_device(struct audio_output *ao)
+{
+ ShoutOutput *sd = (ShoutOutput *)ao;
+
+ close_shout_conn(sd);
+}
+
+static bool
+shout_connect(ShoutOutput *sd, GError **error)
+{
+ switch (shout_open(sd->shout_conn)) {
+ case SHOUTERR_SUCCESS:
+ case SHOUTERR_CONNECTED:
+ return true;
+
+ default:
+ g_set_error(error, shout_output_quark(), 0,
+ "problem opening connection to shout server %s:%i: %s",
+ shout_get_host(sd->shout_conn),
+ shout_get_port(sd->shout_conn),
+ shout_get_error(sd->shout_conn));
+ return false;
+ }
+}
+
+static bool
+my_shout_open_device(struct audio_output *ao, AudioFormat &audio_format,
+ GError **error)
+{
+ ShoutOutput *sd = (ShoutOutput *)ao;
+
+ if (!shout_connect(sd, error))
+ return false;
+
+ if (!encoder_open(sd->encoder, audio_format, error)) {
+ shout_close(sd->shout_conn);
+ return false;
+ }
+
+ if (!write_page(sd, error)) {
+ encoder_close(sd->encoder);
+ shout_close(sd->shout_conn);
+ return false;
+ }
+
+ return true;
+}
+
+static unsigned
+my_shout_delay(struct audio_output *ao)
+{
+ ShoutOutput *sd = (ShoutOutput *)ao;
+
+ int delay = shout_delay(sd->shout_conn);
+ if (delay < 0)
+ delay = 0;
+
+ return delay;
+}
+
+static size_t
+my_shout_play(struct audio_output *ao, const void *chunk, size_t size,
+ GError **error)
+{
+ ShoutOutput *sd = (ShoutOutput *)ao;
+
+ return encoder_write(sd->encoder, chunk, size, error) &&
+ write_page(sd, error)
+ ? size
+ : 0;
+}
+
+static bool
+my_shout_pause(struct audio_output *ao)
+{
+ static char silence[1020];
+
+ return my_shout_play(ao, silence, sizeof(silence), nullptr);
+}
+
+static void
+shout_tag_to_metadata(const Tag *tag, char *dest, size_t size)
+{
+ char artist[size];
+ char title[size];
+
+ artist[0] = 0;
+ title[0] = 0;
+
+ for (unsigned i = 0; i < tag->num_items; i++) {
+ switch (tag->items[i]->type) {
+ case TAG_ARTIST:
+ strncpy(artist, tag->items[i]->value, size);
+ break;
+ case TAG_TITLE:
+ strncpy(title, tag->items[i]->value, size);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ snprintf(dest, size, "%s - %s", artist, title);
+}
+
+static void my_shout_set_tag(struct audio_output *ao,
+ const Tag *tag)
+{
+ ShoutOutput *sd = (ShoutOutput *)ao;
+ GError *error = nullptr;
+
+ if (sd->encoder->plugin.tag != nullptr) {
+ /* encoder plugin supports stream tags */
+
+ if (!encoder_pre_tag(sd->encoder, &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return;
+ }
+
+ if (!write_page(sd, nullptr))
+ return;
+
+ if (!encoder_tag(sd->encoder, tag, &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ }
+ } else {
+ /* no stream tag support: fall back to icy-metadata */
+ char song[1024];
+ shout_tag_to_metadata(tag, song, sizeof(song));
+
+ shout_metadata_add(sd->shout_meta, "song", song);
+ if (SHOUTERR_SUCCESS != shout_set_metadata(sd->shout_conn,
+ sd->shout_meta)) {
+ g_warning("error setting shout metadata\n");
+ }
+ }
+
+ write_page(sd, nullptr);
+}
+
+const struct audio_output_plugin shout_output_plugin = {
+ "shout",
+ nullptr,
+ my_shout_init_driver,
+ my_shout_finish_driver,
+ nullptr,
+ nullptr,
+ my_shout_open_device,
+ my_shout_close_device,
+ my_shout_delay,
+ my_shout_set_tag,
+ my_shout_play,
+ nullptr,
+ my_shout_drop_buffered_audio,
+ my_shout_pause,
+ nullptr,
+};
diff --git a/src/output/ShoutOutputPlugin.hxx b/src/output/ShoutOutputPlugin.hxx
new file mode 100644
index 000000000..496b77975
--- /dev/null
+++ b/src/output/ShoutOutputPlugin.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_SHOUT_OUTPUT_PLUGIN_HXX
+#define MPD_SHOUT_OUTPUT_PLUGIN_HXX
+
+extern const struct audio_output_plugin shout_output_plugin;
+
+#endif
diff --git a/src/output/SolarisOutputPlugin.cxx b/src/output/SolarisOutputPlugin.cxx
new file mode 100644
index 000000000..074eae728
--- /dev/null
+++ b/src/output/SolarisOutputPlugin.cxx
@@ -0,0 +1,218 @@
+/*
+ * 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 "SolarisOutputPlugin.hxx"
+#include "OutputAPI.hxx"
+#include "fd_util.h"
+
+#include <glib.h>
+
+#include <sys/stropts.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#ifdef __sun
+#include <sys/audio.h>
+#else
+
+/* some fake declarations that allow build this plugin on systems
+ other than Solaris, just to see if it compiles */
+
+#define AUDIO_GETINFO 0
+#define AUDIO_SETINFO 0
+#define AUDIO_ENCODING_LINEAR 0
+
+struct audio_info {
+ struct {
+ unsigned sample_rate, channels, precision, encoding;
+ } play;
+};
+
+#endif
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "solaris_output"
+
+struct SolarisOutput {
+ struct audio_output base;
+
+ /* configuration */
+ const char *device;
+
+ int fd;
+
+ bool Initialize(const config_param &param, GError **error_r) {
+ return ao_base_init(&base, &solaris_output_plugin, param,
+ error_r);
+ }
+
+ void Deinitialize() {
+ ao_base_finish(&base);
+ }
+};
+
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+solaris_output_quark(void)
+{
+ return g_quark_from_static_string("solaris_output");
+}
+
+static bool
+solaris_output_test_default_device(void)
+{
+ struct stat st;
+
+ return stat("/dev/audio", &st) == 0 && S_ISCHR(st.st_mode) &&
+ access("/dev/audio", W_OK) == 0;
+}
+
+static struct audio_output *
+solaris_output_init(const config_param &param, GError **error_r)
+{
+ SolarisOutput *so = new SolarisOutput();
+ if (!so->Initialize(param, error_r)) {
+ delete so;
+ return nullptr;
+ }
+
+ so->device = param.GetBlockValue("device", "/dev/audio");
+
+ return &so->base;
+}
+
+static void
+solaris_output_finish(struct audio_output *ao)
+{
+ SolarisOutput *so = (SolarisOutput *)ao;
+
+ so->Deinitialize();
+ delete so;
+}
+
+static bool
+solaris_output_open(struct audio_output *ao, AudioFormat &audio_format,
+ GError **error)
+{
+ SolarisOutput *so = (SolarisOutput *)ao;
+ struct audio_info info;
+ int ret, flags;
+
+ /* support only 16 bit mono/stereo for now; nothing else has
+ been tested */
+ audio_format.format = SampleFormat::S16;
+
+ /* open the device in non-blocking mode */
+
+ so->fd = open_cloexec(so->device, O_WRONLY|O_NONBLOCK, 0);
+ if (so->fd < 0) {
+ g_set_error(error, solaris_output_quark(), errno,
+ "Failed to open %s: %s",
+ so->device, g_strerror(errno));
+ return false;
+ }
+
+ /* restore blocking mode */
+
+ flags = fcntl(so->fd, F_GETFL);
+ if (flags > 0 && (flags & O_NONBLOCK) != 0)
+ fcntl(so->fd, F_SETFL, flags & ~O_NONBLOCK);
+
+ /* configure the audio device */
+
+ ret = ioctl(so->fd, AUDIO_GETINFO, &info);
+ if (ret < 0) {
+ g_set_error(error, solaris_output_quark(), errno,
+ "AUDIO_GETINFO failed: %s", g_strerror(errno));
+ close(so->fd);
+ return false;
+ }
+
+ info.play.sample_rate = audio_format.sample_rate;
+ info.play.channels = audio_format.channels;
+ info.play.precision = 16;
+ info.play.encoding = AUDIO_ENCODING_LINEAR;
+
+ ret = ioctl(so->fd, AUDIO_SETINFO, &info);
+ if (ret < 0) {
+ g_set_error(error, solaris_output_quark(), errno,
+ "AUDIO_SETINFO failed: %s", g_strerror(errno));
+ close(so->fd);
+ return false;
+ }
+
+ return true;
+}
+
+static void
+solaris_output_close(struct audio_output *ao)
+{
+ SolarisOutput *so = (SolarisOutput *)ao;
+
+ close(so->fd);
+}
+
+static size_t
+solaris_output_play(struct audio_output *ao, const void *chunk, size_t size,
+ GError **error)
+{
+ SolarisOutput *so = (SolarisOutput *)ao;
+ ssize_t nbytes;
+
+ nbytes = write(so->fd, chunk, size);
+ if (nbytes <= 0) {
+ g_set_error(error, solaris_output_quark(), errno,
+ "Write failed: %s", g_strerror(errno));
+ return 0;
+ }
+
+ return nbytes;
+}
+
+static void
+solaris_output_cancel(struct audio_output *ao)
+{
+ SolarisOutput *so = (SolarisOutput *)ao;
+
+ ioctl(so->fd, I_FLUSH);
+}
+
+const struct audio_output_plugin solaris_output_plugin = {
+ "solaris",
+ solaris_output_test_default_device,
+ solaris_output_init,
+ solaris_output_finish,
+ nullptr,
+ nullptr,
+ solaris_output_open,
+ solaris_output_close,
+ nullptr,
+ nullptr,
+ solaris_output_play,
+ nullptr,
+ solaris_output_cancel,
+ nullptr,
+ nullptr,
+};
diff --git a/src/output/SolarisOutputPlugin.hxx b/src/output/SolarisOutputPlugin.hxx
new file mode 100644
index 000000000..d0fbd32c8
--- /dev/null
+++ b/src/output/SolarisOutputPlugin.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_SOLARIS_OUTPUT_PLUGIN_HXX
+#define MPD_SOLARIS_OUTPUT_PLUGIN_HXX
+
+extern const struct audio_output_plugin solaris_output_plugin;
+
+#endif
diff --git a/src/output/WinmmOutputPlugin.cxx b/src/output/WinmmOutputPlugin.cxx
new file mode 100644
index 000000000..d02b52c58
--- /dev/null
+++ b/src/output/WinmmOutputPlugin.cxx
@@ -0,0 +1,360 @@
+/*
+ * 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 "WinmmOutputPlugin.hxx"
+#include "OutputAPI.hxx"
+#include "pcm/PcmBuffer.hxx"
+#include "MixerList.hxx"
+
+#include <stdlib.h>
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "winmm_output"
+
+struct WinmmBuffer {
+ PcmBuffer buffer;
+
+ WAVEHDR hdr;
+};
+
+struct WinmmOutput {
+ struct audio_output base;
+
+ UINT device_id;
+ HWAVEOUT handle;
+
+ /**
+ * This event is triggered by Windows when a buffer is
+ * finished.
+ */
+ HANDLE event;
+
+ WinmmBuffer buffers[8];
+ unsigned next_buffer;
+};
+
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+winmm_output_quark(void)
+{
+ return g_quark_from_static_string("winmm_output");
+}
+
+HWAVEOUT
+winmm_output_get_handle(WinmmOutput *output)
+{
+ return output->handle;
+}
+
+static bool
+winmm_output_test_default_device(void)
+{
+ return waveOutGetNumDevs() > 0;
+}
+
+static bool
+get_device_id(const char *device_name, UINT *device_id, GError **error_r)
+{
+ /* if device is not specified use wave mapper */
+ if (device_name == nullptr) {
+ *device_id = WAVE_MAPPER;
+ return true;
+ }
+
+ UINT numdevs = waveOutGetNumDevs();
+
+ /* check for device id */
+ char *endptr;
+ UINT id = strtoul(device_name, &endptr, 0);
+ if (endptr > device_name && *endptr == 0) {
+ if (id >= numdevs)
+ goto fail;
+ *device_id = id;
+ return true;
+ }
+
+ /* check for device name */
+ for (UINT i = 0; i < numdevs; i++) {
+ WAVEOUTCAPS caps;
+ MMRESULT result = waveOutGetDevCaps(i, &caps, sizeof(caps));
+ if (result != MMSYSERR_NOERROR)
+ continue;
+ /* szPname is only 32 chars long, so it is often truncated.
+ Use partial match to work around this. */
+ if (strstr(device_name, caps.szPname) == device_name) {
+ *device_id = i;
+ return true;
+ }
+ }
+
+fail:
+ g_set_error(error_r, winmm_output_quark(), 0,
+ "device \"%s\" is not found", device_name);
+ return false;
+}
+
+static struct audio_output *
+winmm_output_init(const config_param &param, GError **error_r)
+{
+ WinmmOutput *wo = new WinmmOutput();
+ if (!ao_base_init(&wo->base, &winmm_output_plugin, param, error_r)) {
+ g_free(wo);
+ return nullptr;
+ }
+
+ const char *device = param.GetBlockValue("device");
+ if (!get_device_id(device, &wo->device_id, error_r)) {
+ ao_base_finish(&wo->base);
+ g_free(wo);
+ return nullptr;
+ }
+
+ return &wo->base;
+}
+
+static void
+winmm_output_finish(struct audio_output *ao)
+{
+ WinmmOutput *wo = (WinmmOutput *)ao;
+
+ ao_base_finish(&wo->base);
+ delete wo;
+}
+
+static bool
+winmm_output_open(struct audio_output *ao, AudioFormat &audio_format,
+ GError **error_r)
+{
+ WinmmOutput *wo = (WinmmOutput *)ao;
+
+ wo->event = CreateEvent(nullptr, false, false, nullptr);
+ if (wo->event == nullptr) {
+ g_set_error(error_r, winmm_output_quark(), 0,
+ "CreateEvent() failed");
+ return false;
+ }
+
+ switch (audio_format.format) {
+ case SampleFormat::S8:
+ case SampleFormat::S16:
+ break;
+
+ case SampleFormat::S24_P32:
+ case SampleFormat::S32:
+ case SampleFormat::FLOAT:
+ case SampleFormat::DSD:
+ case SampleFormat::UNDEFINED:
+ /* we havn't tested formats other than S16 */
+ audio_format.format = SampleFormat::S16;
+ break;
+ }
+
+ if (audio_format.channels > 2)
+ /* same here: more than stereo was not tested */
+ audio_format.channels = 2;
+
+ WAVEFORMATEX format;
+ format.wFormatTag = WAVE_FORMAT_PCM;
+ format.nChannels = audio_format.channels;
+ format.nSamplesPerSec = audio_format.sample_rate;
+ format.nBlockAlign = audio_format.GetFrameSize();
+ format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
+ format.wBitsPerSample = audio_format.GetSampleSize() * 8;
+ format.cbSize = 0;
+
+ MMRESULT result = waveOutOpen(&wo->handle, wo->device_id, &format,
+ (DWORD_PTR)wo->event, 0, CALLBACK_EVENT);
+ if (result != MMSYSERR_NOERROR) {
+ CloseHandle(wo->event);
+ g_set_error(error_r, winmm_output_quark(), result,
+ "waveOutOpen() failed");
+ return false;
+ }
+
+ for (unsigned i = 0; i < G_N_ELEMENTS(wo->buffers); ++i) {
+ memset(&wo->buffers[i].hdr, 0, sizeof(wo->buffers[i].hdr));
+ }
+
+ wo->next_buffer = 0;
+
+ return true;
+}
+
+static void
+winmm_output_close(struct audio_output *ao)
+{
+ WinmmOutput *wo = (WinmmOutput *)ao;
+
+ for (unsigned i = 0; i < G_N_ELEMENTS(wo->buffers); ++i)
+ wo->buffers[i].buffer.Clear();
+
+ waveOutClose(wo->handle);
+
+ CloseHandle(wo->event);
+}
+
+/**
+ * Copy data into a buffer, and prepare the wave header.
+ */
+static bool
+winmm_set_buffer(WinmmOutput *wo, WinmmBuffer *buffer,
+ const void *data, size_t size,
+ GError **error_r)
+{
+ void *dest = buffer->buffer.Get(size);
+ assert(dest != nullptr);
+
+ memcpy(dest, data, size);
+
+ memset(&buffer->hdr, 0, sizeof(buffer->hdr));
+ buffer->hdr.lpData = (LPSTR)dest;
+ buffer->hdr.dwBufferLength = size;
+
+ MMRESULT result = waveOutPrepareHeader(wo->handle, &buffer->hdr,
+ sizeof(buffer->hdr));
+ if (result != MMSYSERR_NOERROR) {
+ g_set_error(error_r, winmm_output_quark(), result,
+ "waveOutPrepareHeader() failed");
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Wait until the buffer is finished.
+ */
+static bool
+winmm_drain_buffer(WinmmOutput *wo, WinmmBuffer *buffer,
+ GError **error_r)
+{
+ if ((buffer->hdr.dwFlags & WHDR_DONE) == WHDR_DONE)
+ /* already finished */
+ return true;
+
+ while (true) {
+ MMRESULT result = waveOutUnprepareHeader(wo->handle,
+ &buffer->hdr,
+ sizeof(buffer->hdr));
+ if (result == MMSYSERR_NOERROR)
+ return true;
+ else if (result != WAVERR_STILLPLAYING) {
+ g_set_error(error_r, winmm_output_quark(), result,
+ "waveOutUnprepareHeader() failed");
+ return false;
+ }
+
+ /* wait some more */
+ WaitForSingleObject(wo->event, INFINITE);
+ }
+}
+
+static size_t
+winmm_output_play(struct audio_output *ao, const void *chunk, size_t size, GError **error_r)
+{
+ WinmmOutput *wo = (WinmmOutput *)ao;
+
+ /* get the next buffer from the ring and prepare it */
+ WinmmBuffer *buffer = &wo->buffers[wo->next_buffer];
+ if (!winmm_drain_buffer(wo, buffer, error_r) ||
+ !winmm_set_buffer(wo, buffer, chunk, size, error_r))
+ return 0;
+
+ /* enqueue the buffer */
+ MMRESULT result = waveOutWrite(wo->handle, &buffer->hdr,
+ sizeof(buffer->hdr));
+ if (result != MMSYSERR_NOERROR) {
+ waveOutUnprepareHeader(wo->handle, &buffer->hdr,
+ sizeof(buffer->hdr));
+ g_set_error(error_r, winmm_output_quark(), result,
+ "waveOutWrite() failed");
+ return 0;
+ }
+
+ /* mark our buffer as "used" */
+ wo->next_buffer = (wo->next_buffer + 1) %
+ G_N_ELEMENTS(wo->buffers);
+
+ return size;
+}
+
+static bool
+winmm_drain_all_buffers(WinmmOutput *wo, GError **error_r)
+{
+ for (unsigned i = wo->next_buffer; i < G_N_ELEMENTS(wo->buffers); ++i)
+ if (!winmm_drain_buffer(wo, &wo->buffers[i], error_r))
+ return false;
+
+ for (unsigned i = 0; i < wo->next_buffer; ++i)
+ if (!winmm_drain_buffer(wo, &wo->buffers[i], error_r))
+ return false;
+
+ return true;
+}
+
+static void
+winmm_stop(WinmmOutput *wo)
+{
+ waveOutReset(wo->handle);
+
+ for (unsigned i = 0; i < G_N_ELEMENTS(wo->buffers); ++i) {
+ WinmmBuffer *buffer = &wo->buffers[i];
+ waveOutUnprepareHeader(wo->handle, &buffer->hdr,
+ sizeof(buffer->hdr));
+ }
+}
+
+static void
+winmm_output_drain(struct audio_output *ao)
+{
+ WinmmOutput *wo = (WinmmOutput *)ao;
+
+ if (!winmm_drain_all_buffers(wo, nullptr))
+ winmm_stop(wo);
+}
+
+static void
+winmm_output_cancel(struct audio_output *ao)
+{
+ WinmmOutput *wo = (WinmmOutput *)ao;
+
+ winmm_stop(wo);
+}
+
+const struct audio_output_plugin winmm_output_plugin = {
+ "winmm",
+ winmm_output_test_default_device,
+ winmm_output_init,
+ winmm_output_finish,
+ nullptr,
+ nullptr,
+ winmm_output_open,
+ winmm_output_close,
+ nullptr,
+ nullptr,
+ winmm_output_play,
+ winmm_output_drain,
+ winmm_output_cancel,
+ nullptr,
+ &winmm_mixer_plugin,
+};
diff --git a/src/output/WinmmOutputPlugin.hxx b/src/output/WinmmOutputPlugin.hxx
new file mode 100644
index 000000000..e8688782e
--- /dev/null
+++ b/src/output/WinmmOutputPlugin.hxx
@@ -0,0 +1,42 @@
+/*
+ * 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_WINMM_OUTPUT_PLUGIN_HXX
+#define MPD_WINMM_OUTPUT_PLUGIN_HXX
+
+#include "check.h"
+
+#ifdef ENABLE_WINMM_OUTPUT
+
+#include "gcc.h"
+
+#include <windows.h>
+#include <mmsystem.h>
+
+struct WinmmOutput;
+
+extern const struct audio_output_plugin winmm_output_plugin;
+
+gcc_pure
+HWAVEOUT
+winmm_output_get_handle(WinmmOutput *);
+
+#endif
+
+#endif
diff --git a/src/output/alsa_output_plugin.c b/src/output/alsa_output_plugin.c
deleted file mode 100644
index d8b184273..000000000
--- a/src/output/alsa_output_plugin.c
+++ /dev/null
@@ -1,819 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "alsa_output_plugin.h"
-#include "output_api.h"
-#include "mixer_list.h"
-#include "pcm_export.h"
-
-#include <glib.h>
-#include <alsa/asoundlib.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "alsa"
-
-#define ALSA_PCM_NEW_HW_PARAMS_API
-#define ALSA_PCM_NEW_SW_PARAMS_API
-
-static const char default_device[] = "default";
-
-enum {
- MPD_ALSA_BUFFER_TIME_US = 500000,
-};
-
-#define MPD_ALSA_RETRY_NR 5
-
-typedef snd_pcm_sframes_t alsa_writei_t(snd_pcm_t * pcm, const void *buffer,
- snd_pcm_uframes_t size);
-
-struct alsa_data {
- struct audio_output base;
-
- struct pcm_export_state export;
-
- /** the configured name of the ALSA device; NULL for the
- default device */
- char *device;
-
- /** use memory mapped I/O? */
- bool use_mmap;
-
- /**
- * Enable DSD over USB according to the dCS suggested
- * standard?
- *
- * @see http://www.dcsltd.co.uk/page/assets/DSDoverUSB.pdf
- */
- bool dsd_usb;
-
- /** libasound's buffer_time setting (in microseconds) */
- unsigned int buffer_time;
-
- /** libasound's period_time setting (in microseconds) */
- unsigned int period_time;
-
- /** the mode flags passed to snd_pcm_open */
- int mode;
-
- /** the libasound PCM device handle */
- snd_pcm_t *pcm;
-
- /**
- * a pointer to the libasound writei() function, which is
- * snd_pcm_writei() or snd_pcm_mmap_writei(), depending on the
- * use_mmap configuration
- */
- alsa_writei_t *writei;
-
- /**
- * The size of one audio frame passed to method play().
- */
- size_t in_frame_size;
-
- /**
- * The size of one audio frame passed to libasound.
- */
- size_t out_frame_size;
-
- /**
- * The size of one period, in number of frames.
- */
- snd_pcm_uframes_t period_frames;
-
- /**
- * The number of frames written in the current period.
- */
- snd_pcm_uframes_t period_position;
-};
-
-/**
- * The quark used for GError.domain.
- */
-static inline GQuark
-alsa_output_quark(void)
-{
- return g_quark_from_static_string("alsa_output");
-}
-
-static const char *
-alsa_device(const struct alsa_data *ad)
-{
- return ad->device != NULL ? ad->device : default_device;
-}
-
-static struct alsa_data *
-alsa_data_new(void)
-{
- struct alsa_data *ret = g_new(struct alsa_data, 1);
-
- ret->mode = 0;
- ret->writei = snd_pcm_writei;
-
- return ret;
-}
-
-static void
-alsa_configure(struct alsa_data *ad, const struct config_param *param)
-{
- ad->device = config_dup_block_string(param, "device", NULL);
-
- ad->use_mmap = config_get_block_bool(param, "use_mmap", false);
-
- ad->dsd_usb = config_get_block_bool(param, "dsd_usb", false);
-
- ad->buffer_time = config_get_block_unsigned(param, "buffer_time",
- MPD_ALSA_BUFFER_TIME_US);
- ad->period_time = config_get_block_unsigned(param, "period_time", 0);
-
-#ifdef SND_PCM_NO_AUTO_RESAMPLE
- if (!config_get_block_bool(param, "auto_resample", true))
- ad->mode |= SND_PCM_NO_AUTO_RESAMPLE;
-#endif
-
-#ifdef SND_PCM_NO_AUTO_CHANNELS
- if (!config_get_block_bool(param, "auto_channels", true))
- ad->mode |= SND_PCM_NO_AUTO_CHANNELS;
-#endif
-
-#ifdef SND_PCM_NO_AUTO_FORMAT
- if (!config_get_block_bool(param, "auto_format", true))
- ad->mode |= SND_PCM_NO_AUTO_FORMAT;
-#endif
-}
-
-static struct audio_output *
-alsa_init(const struct config_param *param, GError **error_r)
-{
- struct alsa_data *ad = alsa_data_new();
-
- if (!ao_base_init(&ad->base, &alsa_output_plugin, param, error_r)) {
- g_free(ad);
- return NULL;
- }
-
- alsa_configure(ad, param);
-
- return &ad->base;
-}
-
-static void
-alsa_finish(struct audio_output *ao)
-{
- struct alsa_data *ad = (struct alsa_data *)ao;
-
- ao_base_finish(&ad->base);
-
- g_free(ad->device);
- g_free(ad);
-
- /* free libasound's config cache */
- snd_config_update_free_global();
-}
-
-static bool
-alsa_output_enable(struct audio_output *ao, G_GNUC_UNUSED GError **error_r)
-{
- struct alsa_data *ad = (struct alsa_data *)ao;
-
- pcm_export_init(&ad->export);
- return true;
-}
-
-static void
-alsa_output_disable(struct audio_output *ao)
-{
- struct alsa_data *ad = (struct alsa_data *)ao;
-
- pcm_export_deinit(&ad->export);
-}
-
-static bool
-alsa_test_default_device(void)
-{
- snd_pcm_t *handle;
-
- int ret = snd_pcm_open(&handle, default_device,
- SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
- if (ret) {
- g_message("Error opening default ALSA device: %s\n",
- snd_strerror(-ret));
- return false;
- } else
- snd_pcm_close(handle);
-
- return true;
-}
-
-static snd_pcm_format_t
-get_bitformat(enum sample_format sample_format)
-{
- switch (sample_format) {
- case SAMPLE_FORMAT_UNDEFINED:
- case SAMPLE_FORMAT_DSD:
- return SND_PCM_FORMAT_UNKNOWN;
-
- case SAMPLE_FORMAT_S8:
- return SND_PCM_FORMAT_S8;
-
- case SAMPLE_FORMAT_S16:
- return SND_PCM_FORMAT_S16;
-
- case SAMPLE_FORMAT_S24_P32:
- return SND_PCM_FORMAT_S24;
-
- case SAMPLE_FORMAT_S32:
- return SND_PCM_FORMAT_S32;
-
- case SAMPLE_FORMAT_FLOAT:
- return SND_PCM_FORMAT_FLOAT;
- }
-
- assert(false);
- return SND_PCM_FORMAT_UNKNOWN;
-}
-
-static snd_pcm_format_t
-byteswap_bitformat(snd_pcm_format_t fmt)
-{
- switch(fmt) {
- case SND_PCM_FORMAT_S16_LE: return SND_PCM_FORMAT_S16_BE;
- case SND_PCM_FORMAT_S24_LE: return SND_PCM_FORMAT_S24_BE;
- case SND_PCM_FORMAT_S32_LE: return SND_PCM_FORMAT_S32_BE;
- case SND_PCM_FORMAT_S16_BE: return SND_PCM_FORMAT_S16_LE;
- case SND_PCM_FORMAT_S24_BE: return SND_PCM_FORMAT_S24_LE;
-
- case SND_PCM_FORMAT_S24_3BE:
- return SND_PCM_FORMAT_S24_3LE;
-
- case SND_PCM_FORMAT_S24_3LE:
- return SND_PCM_FORMAT_S24_3BE;
-
- case SND_PCM_FORMAT_S32_BE: return SND_PCM_FORMAT_S32_LE;
- default: return SND_PCM_FORMAT_UNKNOWN;
- }
-}
-
-static snd_pcm_format_t
-alsa_to_packed_format(snd_pcm_format_t fmt)
-{
- switch (fmt) {
- case SND_PCM_FORMAT_S24_LE:
- return SND_PCM_FORMAT_S24_3LE;
-
- case SND_PCM_FORMAT_S24_BE:
- return SND_PCM_FORMAT_S24_3BE;
-
- default:
- return SND_PCM_FORMAT_UNKNOWN;
- }
-}
-
-static int
-alsa_try_format_or_packed(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
- snd_pcm_format_t fmt, bool *packed_r)
-{
- int err = snd_pcm_hw_params_set_format(pcm, hwparams, fmt);
- if (err == 0)
- *packed_r = false;
-
- if (err != -EINVAL)
- return err;
-
- fmt = alsa_to_packed_format(fmt);
- if (fmt == SND_PCM_FORMAT_UNKNOWN)
- return -EINVAL;
-
- err = snd_pcm_hw_params_set_format(pcm, hwparams, fmt);
- if (err == 0)
- *packed_r = true;
-
- return err;
-}
-
-/**
- * Attempts to configure the specified sample format, and tries the
- * reversed host byte order if was not supported.
- */
-static int
-alsa_output_try_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
- enum sample_format sample_format,
- bool *packed_r, bool *reverse_endian_r)
-{
- snd_pcm_format_t alsa_format = get_bitformat(sample_format);
- if (alsa_format == SND_PCM_FORMAT_UNKNOWN)
- return -EINVAL;
-
- int err = alsa_try_format_or_packed(pcm, hwparams, alsa_format,
- packed_r);
- if (err == 0)
- *reverse_endian_r = false;
-
- if (err != -EINVAL)
- return err;
-
- alsa_format = byteswap_bitformat(alsa_format);
- if (alsa_format == SND_PCM_FORMAT_UNKNOWN)
- return -EINVAL;
-
- err = alsa_try_format_or_packed(pcm, hwparams, alsa_format, packed_r);
- if (err == 0)
- *reverse_endian_r = true;
-
- return err;
-}
-
-/**
- * Configure a sample format, and probe other formats if that fails.
- */
-static int
-alsa_output_setup_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
- struct audio_format *audio_format,
- bool *packed_r, bool *reverse_endian_r)
-{
- /* try the input format first */
-
- int err = alsa_output_try_format(pcm, hwparams, audio_format->format,
- packed_r, reverse_endian_r);
-
- /* if unsupported by the hardware, try other formats */
-
- static const enum sample_format probe_formats[] = {
- SAMPLE_FORMAT_S24_P32,
- SAMPLE_FORMAT_S32,
- SAMPLE_FORMAT_S16,
- SAMPLE_FORMAT_S8,
- SAMPLE_FORMAT_UNDEFINED,
- };
-
- for (unsigned i = 0;
- err == -EINVAL && probe_formats[i] != SAMPLE_FORMAT_UNDEFINED;
- ++i) {
- const enum sample_format mpd_format = probe_formats[i];
- if (mpd_format == audio_format->format)
- continue;
-
- err = alsa_output_try_format(pcm, hwparams, mpd_format,
- packed_r, reverse_endian_r);
- if (err == 0)
- audio_format->format = mpd_format;
- }
-
- return err;
-}
-
-/**
- * Set up the snd_pcm_t object which was opened by the caller. Set up
- * the configured settings and the audio format.
- */
-static bool
-alsa_setup(struct alsa_data *ad, struct audio_format *audio_format,
- bool *packed_r, bool *reverse_endian_r, GError **error)
-{
- snd_pcm_hw_params_t *hwparams;
- snd_pcm_sw_params_t *swparams;
- unsigned int sample_rate = audio_format->sample_rate;
- unsigned int channels = audio_format->channels;
- snd_pcm_uframes_t alsa_buffer_size;
- snd_pcm_uframes_t alsa_period_size;
- int err;
- const char *cmd = NULL;
- int retry = MPD_ALSA_RETRY_NR;
- unsigned int period_time, period_time_ro;
- unsigned int buffer_time;
-
- period_time_ro = period_time = ad->period_time;
-configure_hw:
- /* configure HW params */
- snd_pcm_hw_params_alloca(&hwparams);
- cmd = "snd_pcm_hw_params_any";
- err = snd_pcm_hw_params_any(ad->pcm, hwparams);
- if (err < 0)
- goto error;
-
- if (ad->use_mmap) {
- err = snd_pcm_hw_params_set_access(ad->pcm, hwparams,
- SND_PCM_ACCESS_MMAP_INTERLEAVED);
- if (err < 0) {
- g_warning("Cannot set mmap'ed mode on ALSA device \"%s\": %s\n",
- alsa_device(ad), snd_strerror(-err));
- g_warning("Falling back to direct write mode\n");
- ad->use_mmap = false;
- } else
- ad->writei = snd_pcm_mmap_writei;
- }
-
- if (!ad->use_mmap) {
- cmd = "snd_pcm_hw_params_set_access";
- err = snd_pcm_hw_params_set_access(ad->pcm, hwparams,
- SND_PCM_ACCESS_RW_INTERLEAVED);
- if (err < 0)
- goto error;
- ad->writei = snd_pcm_writei;
- }
-
- err = alsa_output_setup_format(ad->pcm, hwparams, audio_format,
- packed_r, reverse_endian_r);
- if (err < 0) {
- g_set_error(error, alsa_output_quark(), err,
- "ALSA device \"%s\" does not support format %s: %s",
- alsa_device(ad),
- sample_format_to_string(audio_format->format),
- snd_strerror(-err));
- return false;
- }
-
- snd_pcm_format_t format;
- if (snd_pcm_hw_params_get_format(hwparams, &format) == 0)
- g_debug("format=%s (%s)", snd_pcm_format_name(format),
- snd_pcm_format_description(format));
-
- err = snd_pcm_hw_params_set_channels_near(ad->pcm, hwparams,
- &channels);
- if (err < 0) {
- g_set_error(error, alsa_output_quark(), err,
- "ALSA device \"%s\" does not support %i channels: %s",
- alsa_device(ad), (int)audio_format->channels,
- snd_strerror(-err));
- return false;
- }
- audio_format->channels = (int8_t)channels;
-
- err = snd_pcm_hw_params_set_rate_near(ad->pcm, hwparams,
- &sample_rate, NULL);
- if (err < 0 || sample_rate == 0) {
- g_set_error(error, alsa_output_quark(), err,
- "ALSA device \"%s\" does not support %u Hz audio",
- alsa_device(ad), audio_format->sample_rate);
- return false;
- }
- audio_format->sample_rate = sample_rate;
-
- snd_pcm_uframes_t buffer_size_min, buffer_size_max;
- snd_pcm_hw_params_get_buffer_size_min(hwparams, &buffer_size_min);
- snd_pcm_hw_params_get_buffer_size_max(hwparams, &buffer_size_max);
- unsigned buffer_time_min, buffer_time_max;
- snd_pcm_hw_params_get_buffer_time_min(hwparams, &buffer_time_min, 0);
- snd_pcm_hw_params_get_buffer_time_max(hwparams, &buffer_time_max, 0);
- g_debug("buffer: size=%u..%u time=%u..%u",
- (unsigned)buffer_size_min, (unsigned)buffer_size_max,
- buffer_time_min, buffer_time_max);
-
- snd_pcm_uframes_t period_size_min, period_size_max;
- snd_pcm_hw_params_get_period_size_min(hwparams, &period_size_min, 0);
- snd_pcm_hw_params_get_period_size_max(hwparams, &period_size_max, 0);
- unsigned period_time_min, period_time_max;
- snd_pcm_hw_params_get_period_time_min(hwparams, &period_time_min, 0);
- snd_pcm_hw_params_get_period_time_max(hwparams, &period_time_max, 0);
- g_debug("period: size=%u..%u time=%u..%u",
- (unsigned)period_size_min, (unsigned)period_size_max,
- period_time_min, period_time_max);
-
- if (ad->buffer_time > 0) {
- buffer_time = ad->buffer_time;
- cmd = "snd_pcm_hw_params_set_buffer_time_near";
- err = snd_pcm_hw_params_set_buffer_time_near(ad->pcm, hwparams,
- &buffer_time, NULL);
- if (err < 0)
- goto error;
- } else {
- err = snd_pcm_hw_params_get_buffer_time(hwparams, &buffer_time,
- NULL);
- if (err < 0)
- buffer_time = 0;
- }
-
- if (period_time_ro == 0 && buffer_time >= 10000) {
- period_time_ro = period_time = buffer_time / 4;
-
- g_debug("default period_time = buffer_time/4 = %u/4 = %u",
- buffer_time, period_time);
- }
-
- if (period_time_ro > 0) {
- period_time = period_time_ro;
- cmd = "snd_pcm_hw_params_set_period_time_near";
- err = snd_pcm_hw_params_set_period_time_near(ad->pcm, hwparams,
- &period_time, NULL);
- if (err < 0)
- goto error;
- }
-
- cmd = "snd_pcm_hw_params";
- err = snd_pcm_hw_params(ad->pcm, hwparams);
- if (err == -EPIPE && --retry > 0 && period_time_ro > 0) {
- period_time_ro = period_time_ro >> 1;
- goto configure_hw;
- } else if (err < 0)
- goto error;
- if (retry != MPD_ALSA_RETRY_NR)
- g_debug("ALSA period_time set to %d\n", period_time);
-
- cmd = "snd_pcm_hw_params_get_buffer_size";
- err = snd_pcm_hw_params_get_buffer_size(hwparams, &alsa_buffer_size);
- if (err < 0)
- goto error;
-
- cmd = "snd_pcm_hw_params_get_period_size";
- err = snd_pcm_hw_params_get_period_size(hwparams, &alsa_period_size,
- NULL);
- if (err < 0)
- goto error;
-
- /* configure SW params */
- snd_pcm_sw_params_alloca(&swparams);
-
- cmd = "snd_pcm_sw_params_current";
- err = snd_pcm_sw_params_current(ad->pcm, swparams);
- if (err < 0)
- goto error;
-
- cmd = "snd_pcm_sw_params_set_start_threshold";
- err = snd_pcm_sw_params_set_start_threshold(ad->pcm, swparams,
- alsa_buffer_size -
- alsa_period_size);
- if (err < 0)
- goto error;
-
- cmd = "snd_pcm_sw_params_set_avail_min";
- err = snd_pcm_sw_params_set_avail_min(ad->pcm, swparams,
- alsa_period_size);
- if (err < 0)
- goto error;
-
- cmd = "snd_pcm_sw_params";
- err = snd_pcm_sw_params(ad->pcm, swparams);
- if (err < 0)
- goto error;
-
- g_debug("buffer_size=%u period_size=%u",
- (unsigned)alsa_buffer_size, (unsigned)alsa_period_size);
-
- if (alsa_period_size == 0)
- /* this works around a SIGFPE bug that occurred when
- an ALSA driver indicated period_size==0; this
- caused a division by zero in alsa_play(). By using
- the fallback "1", we make sure that this won't
- happen again. */
- alsa_period_size = 1;
-
- ad->period_frames = alsa_period_size;
- ad->period_position = 0;
-
- return true;
-
-error:
- g_set_error(error, alsa_output_quark(), err,
- "Error opening ALSA device \"%s\" (%s): %s",
- alsa_device(ad), cmd, snd_strerror(-err));
- return false;
-}
-
-static bool
-alsa_setup_dsd(struct alsa_data *ad, struct audio_format *audio_format,
- bool *shift8_r, bool *packed_r, bool *reverse_endian_r,
- GError **error_r)
-{
- assert(ad->dsd_usb);
- assert(audio_format->format == SAMPLE_FORMAT_DSD);
-
- /* pass 24 bit to alsa_setup() */
-
- struct audio_format usb_format = *audio_format;
- usb_format.format = SAMPLE_FORMAT_S24_P32;
- usb_format.sample_rate /= 2;
-
- const struct audio_format check = usb_format;
-
- if (!alsa_setup(ad, &usb_format, packed_r, reverse_endian_r, error_r))
- return false;
-
- /* if the device allows only 32 bit, shift all DSD-over-USB
- samples left by 8 bit and leave the lower 8 bit cleared;
- the DSD-over-USB documentation does not specify whether
- this is legal, but there is anecdotical evidence that this
- is possible (and the only option for some devices) */
- *shift8_r = usb_format.format == SAMPLE_FORMAT_S32;
- if (usb_format.format == SAMPLE_FORMAT_S32)
- usb_format.format = SAMPLE_FORMAT_S24_P32;
-
- if (!audio_format_equals(&usb_format, &check)) {
- /* no bit-perfect playback, which is required
- for DSD over USB */
- g_set_error(error_r, alsa_output_quark(), 0,
- "Failed to configure DSD-over-USB on ALSA device \"%s\"",
- alsa_device(ad));
- return false;
- }
-
- return true;
-}
-
-static bool
-alsa_setup_or_dsd(struct alsa_data *ad, struct audio_format *audio_format,
- GError **error_r)
-{
- bool shift8 = false, packed, reverse_endian;
-
- const bool dsd_usb = ad->dsd_usb &&
- audio_format->format == SAMPLE_FORMAT_DSD;
- const bool success = dsd_usb
- ? alsa_setup_dsd(ad, audio_format,
- &shift8, &packed, &reverse_endian,
- error_r)
- : alsa_setup(ad, audio_format, &packed, &reverse_endian,
- error_r);
- if (!success)
- return false;
-
- pcm_export_open(&ad->export,
- audio_format->format, audio_format->channels,
- dsd_usb, shift8, packed, reverse_endian);
- return true;
-}
-
-static bool
-alsa_open(struct audio_output *ao, struct audio_format *audio_format, GError **error)
-{
- struct alsa_data *ad = (struct alsa_data *)ao;
- int err;
- bool success;
-
- err = snd_pcm_open(&ad->pcm, alsa_device(ad),
- SND_PCM_STREAM_PLAYBACK, ad->mode);
- if (err < 0) {
- g_set_error(error, alsa_output_quark(), err,
- "Failed to open ALSA device \"%s\": %s",
- alsa_device(ad), snd_strerror(err));
- return false;
- }
-
- g_debug("opened %s type=%s", snd_pcm_name(ad->pcm),
- snd_pcm_type_name(snd_pcm_type(ad->pcm)));
-
- success = alsa_setup_or_dsd(ad, audio_format, error);
- if (!success) {
- snd_pcm_close(ad->pcm);
- return false;
- }
-
- ad->in_frame_size = audio_format_frame_size(audio_format);
- ad->out_frame_size = pcm_export_frame_size(&ad->export, audio_format);
-
- return true;
-}
-
-static int
-alsa_recover(struct alsa_data *ad, int err)
-{
- if (err == -EPIPE) {
- g_debug("Underrun on ALSA device \"%s\"\n", alsa_device(ad));
- } else if (err == -ESTRPIPE) {
- g_debug("ALSA device \"%s\" was suspended\n", alsa_device(ad));
- }
-
- switch (snd_pcm_state(ad->pcm)) {
- case SND_PCM_STATE_PAUSED:
- err = snd_pcm_pause(ad->pcm, /* disable */ 0);
- break;
- case SND_PCM_STATE_SUSPENDED:
- err = snd_pcm_resume(ad->pcm);
- if (err == -EAGAIN)
- return 0;
- /* fall-through to snd_pcm_prepare: */
- case SND_PCM_STATE_SETUP:
- case SND_PCM_STATE_XRUN:
- ad->period_position = 0;
- err = snd_pcm_prepare(ad->pcm);
- break;
- case SND_PCM_STATE_DISCONNECTED:
- break;
- /* this is no error, so just keep running */
- case SND_PCM_STATE_RUNNING:
- err = 0;
- break;
- default:
- /* unknown state, do nothing */
- break;
- }
-
- return err;
-}
-
-static void
-alsa_drain(struct audio_output *ao)
-{
- struct alsa_data *ad = (struct alsa_data *)ao;
-
- if (snd_pcm_state(ad->pcm) != SND_PCM_STATE_RUNNING)
- return;
-
- if (ad->period_position > 0) {
- /* generate some silence to finish the partial
- period */
- snd_pcm_uframes_t nframes =
- ad->period_frames - ad->period_position;
- size_t nbytes = nframes * ad->out_frame_size;
- void *buffer = g_malloc(nbytes);
- snd_pcm_hw_params_t *params;
- snd_pcm_format_t format;
- unsigned channels;
-
- snd_pcm_hw_params_alloca(&params);
- snd_pcm_hw_params_current(ad->pcm, params);
- snd_pcm_hw_params_get_format(params, &format);
- snd_pcm_hw_params_get_channels(params, &channels);
-
- snd_pcm_format_set_silence(format, buffer, nframes * channels);
- ad->writei(ad->pcm, buffer, nframes);
- g_free(buffer);
- }
-
- snd_pcm_drain(ad->pcm);
-
- ad->period_position = 0;
-}
-
-static void
-alsa_cancel(struct audio_output *ao)
-{
- struct alsa_data *ad = (struct alsa_data *)ao;
-
- ad->period_position = 0;
-
- snd_pcm_drop(ad->pcm);
-}
-
-static void
-alsa_close(struct audio_output *ao)
-{
- struct alsa_data *ad = (struct alsa_data *)ao;
-
- snd_pcm_close(ad->pcm);
-}
-
-static size_t
-alsa_play(struct audio_output *ao, const void *chunk, size_t size,
- GError **error)
-{
- struct alsa_data *ad = (struct alsa_data *)ao;
-
- assert(size % ad->in_frame_size == 0);
-
- chunk = pcm_export(&ad->export, chunk, size, &size);
-
- assert(size % ad->out_frame_size == 0);
-
- size /= ad->out_frame_size;
-
- while (true) {
- snd_pcm_sframes_t ret = ad->writei(ad->pcm, chunk, size);
- if (ret > 0) {
- ad->period_position = (ad->period_position + ret)
- % ad->period_frames;
-
- size_t bytes_written = ret * ad->out_frame_size;
- return pcm_export_source_size(&ad->export,
- bytes_written);
- }
-
- if (ret < 0 && ret != -EAGAIN && ret != -EINTR &&
- alsa_recover(ad, ret) < 0) {
- g_set_error(error, alsa_output_quark(), errno,
- "%s", snd_strerror(-errno));
- return 0;
- }
- }
-}
-
-const struct audio_output_plugin alsa_output_plugin = {
- .name = "alsa",
- .test_default_device = alsa_test_default_device,
- .init = alsa_init,
- .finish = alsa_finish,
- .enable = alsa_output_enable,
- .disable = alsa_output_disable,
- .open = alsa_open,
- .play = alsa_play,
- .drain = alsa_drain,
- .cancel = alsa_cancel,
- .close = alsa_close,
-
- .mixer_plugin = &alsa_mixer_plugin,
-};
diff --git a/src/output/alsa_output_plugin.h b/src/output/alsa_output_plugin.h
deleted file mode 100644
index daa1f3615..000000000
--- a/src/output/alsa_output_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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_ALSA_OUTPUT_PLUGIN_H
-#define MPD_ALSA_OUTPUT_PLUGIN_H
-
-extern const struct audio_output_plugin alsa_output_plugin;
-
-#endif
diff --git a/src/output/ao_output_plugin.c b/src/output/ao_output_plugin.c
deleted file mode 100644
index d7e577fa4..000000000
--- a/src/output/ao_output_plugin.c
+++ /dev/null
@@ -1,264 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "ao_output_plugin.h"
-#include "output_api.h"
-
-#include <ao/ao.h>
-#include <glib.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "ao"
-
-/* An ao_sample_format, with all fields set to zero: */
-static const ao_sample_format OUR_AO_FORMAT_INITIALIZER;
-
-static unsigned ao_output_ref;
-
-struct ao_data {
- struct audio_output base;
-
- size_t write_size;
- int driver;
- ao_option *options;
- ao_device *device;
-} AoData;
-
-static inline GQuark
-ao_output_quark(void)
-{
- return g_quark_from_static_string("ao_output");
-}
-
-static void
-ao_output_error(GError **error_r)
-{
- const char *error;
-
- switch (errno) {
- case AO_ENODRIVER:
- error = "No such libao driver";
- break;
-
- case AO_ENOTLIVE:
- error = "This driver is not a libao live device";
- break;
-
- case AO_EBADOPTION:
- error = "Invalid libao option";
- break;
-
- case AO_EOPENDEVICE:
- error = "Cannot open the libao device";
- break;
-
- case AO_EFAIL:
- error = "Generic libao failure";
- break;
-
- default:
- error = g_strerror(errno);
- }
-
- g_set_error(error_r, ao_output_quark(), errno,
- "%s", error);
-}
-
-static struct audio_output *
-ao_output_init(const struct config_param *param,
- GError **error)
-{
- struct ao_data *ad = g_new(struct ao_data, 1);
-
- if (!ao_base_init(&ad->base, &ao_output_plugin, param, error)) {
- g_free(ad);
- return NULL;
- }
-
- ao_info *ai;
- const char *value;
-
- ad->options = NULL;
-
- ad->write_size = config_get_block_unsigned(param, "write_size", 1024);
-
- if (ao_output_ref == 0) {
- ao_initialize();
- }
- ao_output_ref++;
-
- value = config_get_block_string(param, "driver", "default");
- if (0 == strcmp(value, "default"))
- ad->driver = ao_default_driver_id();
- else
- ad->driver = ao_driver_id(value);
-
- if (ad->driver < 0) {
- g_set_error(error, ao_output_quark(), 0,
- "\"%s\" is not a valid ao driver",
- value);
- ao_base_finish(&ad->base);
- g_free(ad);
- return NULL;
- }
-
- if ((ai = ao_driver_info(ad->driver)) == NULL) {
- g_set_error(error, ao_output_quark(), 0,
- "problems getting driver info");
- ao_base_finish(&ad->base);
- g_free(ad);
- return NULL;
- }
-
- g_debug("using ao driver \"%s\" for \"%s\"\n", ai->short_name,
- config_get_block_string(param, "name", NULL));
-
- value = config_get_block_string(param, "options", NULL);
- if (value != NULL) {
- gchar **options = g_strsplit(value, ";", 0);
-
- for (unsigned i = 0; options[i] != NULL; ++i) {
- gchar **key_value = g_strsplit(options[i], "=", 2);
-
- if (key_value[0] == NULL || key_value[1] == NULL) {
- g_set_error(error, ao_output_quark(), 0,
- "problems parsing options \"%s\"",
- options[i]);
- ao_base_finish(&ad->base);
- g_free(ad);
- return NULL;
- }
-
- ao_append_option(&ad->options, key_value[0],
- key_value[1]);
-
- g_strfreev(key_value);
- }
-
- g_strfreev(options);
- }
-
- return &ad->base;
-}
-
-static void
-ao_output_finish(struct audio_output *ao)
-{
- struct ao_data *ad = (struct ao_data *)ao;
-
- ao_free_options(ad->options);
- ao_base_finish(&ad->base);
- g_free(ad);
-
- ao_output_ref--;
-
- if (ao_output_ref == 0)
- ao_shutdown();
-}
-
-static void
-ao_output_close(struct audio_output *ao)
-{
- struct ao_data *ad = (struct ao_data *)ao;
-
- ao_close(ad->device);
-}
-
-static bool
-ao_output_open(struct audio_output *ao, struct audio_format *audio_format,
- GError **error)
-{
- ao_sample_format format = OUR_AO_FORMAT_INITIALIZER;
- struct ao_data *ad = (struct ao_data *)ao;
-
- switch (audio_format->format) {
- case SAMPLE_FORMAT_S8:
- format.bits = 8;
- break;
-
- case SAMPLE_FORMAT_S16:
- format.bits = 16;
- break;
-
- default:
- /* support for 24 bit samples in libao is currently
- dubious, and until we have sorted that out,
- convert everything to 16 bit */
- audio_format->format = SAMPLE_FORMAT_S16;
- format.bits = 16;
- break;
- }
-
- format.rate = audio_format->sample_rate;
- format.byte_format = AO_FMT_NATIVE;
- format.channels = audio_format->channels;
-
- ad->device = ao_open_live(ad->driver, &format, ad->options);
-
- if (ad->device == NULL) {
- ao_output_error(error);
- return false;
- }
-
- return true;
-}
-
-/**
- * For whatever reason, libao wants a non-const pointer. Let's hope
- * it does not write to the buffer, and use the union deconst hack to
- * work around this API misdesign.
- */
-static int ao_play_deconst(ao_device *device, const void *output_samples,
- uint_32 num_bytes)
-{
- union {
- const void *in;
- void *out;
- } u;
-
- u.in = output_samples;
- return ao_play(device, u.out, num_bytes);
-}
-
-static size_t
-ao_output_play(struct audio_output *ao, const void *chunk, size_t size,
- GError **error)
-{
- struct ao_data *ad = (struct ao_data *)ao;
-
- if (size > ad->write_size)
- size = ad->write_size;
-
- if (ao_play_deconst(ad->device, chunk, size) == 0) {
- ao_output_error(error);
- return 0;
- }
-
- return size;
-}
-
-const struct audio_output_plugin ao_output_plugin = {
- .name = "ao",
- .init = ao_output_init,
- .finish = ao_output_finish,
- .open = ao_output_open,
- .close = ao_output_close,
- .play = ao_output_play,
-};
diff --git a/src/output/ao_output_plugin.h b/src/output/ao_output_plugin.h
deleted file mode 100644
index 9a3a47c05..000000000
--- a/src/output/ao_output_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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_AO_OUTPUT_PLUGIN_H
-#define MPD_AO_OUTPUT_PLUGIN_H
-
-extern const struct audio_output_plugin ao_output_plugin;
-
-#endif
diff --git a/src/output/ffado_output_plugin.c b/src/output/ffado_output_plugin.c
deleted file mode 100644
index ba239a4ad..000000000
--- a/src/output/ffado_output_plugin.c
+++ /dev/null
@@ -1,359 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * Warning: this plugin was not tested successfully. I just couldn't
- * keep libffado2 from crashing. Use at your own risk.
- *
- * For details, see my Debian bug reports:
- *
- * http://bugs.debian.org/601657
- * http://bugs.debian.org/601659
- * http://bugs.debian.org/601663
- *
- */
-
-#include "config.h"
-#include "ffado_output_plugin.h"
-#include "output_api.h"
-#include "timer.h"
-
-#include <glib.h>
-#include <assert.h>
-
-#include <libffado/ffado.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "ffado"
-
-enum {
- MAX_STREAMS = 8,
-};
-
-struct mpd_ffado_stream {
- /** libffado's stream number */
- int number;
-
- float *buffer;
-};
-
-struct mpd_ffado_device {
- struct audio_output base;
-
- char *device_name;
- int verbose;
- unsigned period_size, nb_buffers;
-
- ffado_device_t *dev;
-
- /**
- * The current sample position inside the stream buffers. New
- * samples get appended at this position on all streams at the
- * same time. When the buffers are full
- * (buffer_position==period_size),
- * ffado_streaming_transfer_playback_buffers() gets called to
- * hand them over to libffado.
- */
- unsigned buffer_position;
-
- /**
- * The number of streams which are really used by MPD.
- */
- int num_streams;
- struct mpd_ffado_stream streams[MAX_STREAMS];
-};
-
-static inline GQuark
-ffado_output_quark(void)
-{
- return g_quark_from_static_string("ffado_output");
-}
-
-static struct audio_output *
-ffado_init(const struct config_param *param,
- GError **error_r)
-{
- g_debug("using libffado version %s, API=%d",
- ffado_get_version(), ffado_get_api_version());
-
- struct mpd_ffado_device *fd = g_new(struct mpd_ffado_device, 1);
- if (!ao_base_init(&fd->base, &ffado_output_plugin, param, error_r)) {
- g_free(fd);
- return NULL;
- }
-
- fd->device_name = config_dup_block_string(param, "device", NULL);
- fd->verbose = config_get_block_unsigned(param, "verbose", 0);
-
- fd->period_size = config_get_block_unsigned(param, "period_size",
- 1024);
- if (fd->period_size == 0 || fd->period_size > 1024 * 1024) {
- ao_base_finish(&fd->base);
- g_set_error(error_r, ffado_output_quark(), 0,
- "invalid period_size setting");
- return false;
- }
-
- fd->nb_buffers = config_get_block_unsigned(param, "nb_buffers", 3);
- if (fd->nb_buffers == 0 || fd->nb_buffers > 1024) {
- ao_base_finish(&fd->base);
- g_set_error(error_r, ffado_output_quark(), 0,
- "invalid nb_buffers setting");
- return false;
- }
-
- return &fd->base;
-}
-
-static void
-ffado_finish(struct audio_output *ao)
-{
- struct mpd_ffado_device *fd = (struct mpd_ffado_device *)ao;
-
- g_free(fd->device_name);
- ao_base_finish(&fd->base);
- g_free(fd);
-}
-
-static bool
-ffado_configure_stream(ffado_device_t *dev, struct mpd_ffado_stream *stream,
- GError **error_r)
-{
- char *buffer = (char *)stream->buffer;
- if (ffado_streaming_set_playback_stream_buffer(dev, stream->number,
- buffer) != 0) {
- g_set_error(error_r, ffado_output_quark(), 0,
- "failed to configure stream buffer");
- return false;
- }
-
- if (ffado_streaming_playback_stream_onoff(dev, stream->number,
- 1) != 0) {
- g_set_error(error_r, ffado_output_quark(), 0,
- "failed to disable stream");
- return false;
- }
-
- return true;
-}
-
-static bool
-ffado_configure(struct mpd_ffado_device *fd, struct audio_format *audio_format,
- GError **error_r)
-{
- assert(fd != NULL);
- assert(fd->dev != NULL);
- assert(audio_format->channels <= MAX_STREAMS);
-
- if (ffado_streaming_set_audio_datatype(fd->dev,
- ffado_audio_datatype_float) != 0) {
- g_set_error(error_r, ffado_output_quark(), 0,
- "ffado_streaming_set_audio_datatype() failed");
- return false;
- }
-
- int num_streams = ffado_streaming_get_nb_playback_streams(fd->dev);
- if (num_streams < 0) {
- g_set_error(error_r, ffado_output_quark(), 0,
- "ffado_streaming_get_nb_playback_streams() failed");
- return false;
- }
-
- g_debug("there are %d playback streams", num_streams);
-
- fd->num_streams = 0;
- for (int i = 0; i < num_streams; ++i) {
- char name[256];
- ffado_streaming_get_playback_stream_name(fd->dev, i, name,
- sizeof(name) - 1);
-
- ffado_streaming_stream_type type =
- ffado_streaming_get_playback_stream_type(fd->dev, i);
- if (type != ffado_stream_type_audio) {
- g_debug("stream %d name='%s': not an audio stream",
- i, name);
- continue;
- }
-
- if (fd->num_streams >= audio_format->channels) {
- g_debug("stream %d name='%s': ignoring",
- i, name);
- continue;
- }
-
- g_debug("stream %d name='%s'", i, name);
-
- struct mpd_ffado_stream *stream =
- &fd->streams[fd->num_streams++];
-
- stream->number = i;
-
- /* allocated buffer is zeroed = silence */
- stream->buffer = g_new0(float, fd->period_size);
-
- if (!ffado_configure_stream(fd->dev, stream, error_r))
- return false;
- }
-
- if (!audio_valid_channel_count(fd->num_streams)) {
- g_set_error(error_r, ffado_output_quark(), 0,
- "invalid channel count from libffado: %u",
- audio_format->channels);
- return false;
- }
-
- g_debug("configured %d audio streams", fd->num_streams);
-
- if (ffado_streaming_prepare(fd->dev) != 0) {
- g_set_error(error_r, ffado_output_quark(), 0,
- "ffado_streaming_prepare() failed");
- return false;
- }
-
- if (ffado_streaming_start(fd->dev) != 0) {
- g_set_error(error_r, ffado_output_quark(), 0,
- "ffado_streaming_start() failed");
- return false;
- }
-
- audio_format->channels = fd->num_streams;
- return true;
-}
-
-static bool
-ffado_open(struct audio_output *ao, struct audio_format *audio_format,
- GError **error_r)
-{
- struct mpd_ffado_device *fd = (struct mpd_ffado_device *)ao;
-
- /* will be converted to floating point, choose best input
- format */
- audio_format->format = SAMPLE_FORMAT_S24_P32;
-
- ffado_device_info_t device_info;
- memset(&device_info, 0, sizeof(device_info));
- if (fd->device_name != NULL) {
- device_info.nb_device_spec_strings = 1;
- device_info.device_spec_strings = &fd->device_name;
- }
-
- ffado_options_t options;
- memset(&options, 0, sizeof(options));
- options.sample_rate = audio_format->sample_rate;
- options.period_size = fd->period_size;
- options.nb_buffers = fd->nb_buffers;
- options.verbose = fd->verbose;
-
- fd->dev = ffado_streaming_init(device_info, options);
- if (fd->dev == NULL) {
- g_set_error(error_r, ffado_output_quark(), 0,
- "ffado_streaming_init() failed");
- return false;
- }
-
- if (!ffado_configure(fd, audio_format, error_r)) {
- ffado_streaming_finish(fd->dev);
-
- for (int i = 0; i < fd->num_streams; ++i) {
- struct mpd_ffado_stream *stream = &fd->streams[i];
- g_free(stream->buffer);
- }
-
- return false;
- }
-
- fd->buffer_position = 0;
-
- return true;
-}
-
-static void
-ffado_close(struct audio_output *ao)
-{
- struct mpd_ffado_device *fd = (struct mpd_ffado_device *)ao;
-
- ffado_streaming_stop(fd->dev);
- ffado_streaming_finish(fd->dev);
-
- for (int i = 0; i < fd->num_streams; ++i) {
- struct mpd_ffado_stream *stream = &fd->streams[i];
- g_free(stream->buffer);
- }
-}
-
-static size_t
-ffado_play(struct audio_output *ao, const void *chunk, size_t size,
- GError **error_r)
-{
- struct mpd_ffado_device *fd = (struct mpd_ffado_device *)ao;
-
- /* wait for prefious buffer to finish (if it was full) */
-
- if (fd->buffer_position >= fd->period_size) {
- switch (ffado_streaming_wait(fd->dev)) {
- case ffado_wait_ok:
- case ffado_wait_xrun:
- break;
-
- default:
- g_set_error(error_r, ffado_output_quark(), 0,
- "ffado_streaming_wait() failed");
- return 0;
- }
-
- fd->buffer_position = 0;
- }
-
- /* copy samples to stream buffers, non-interleaved */
-
- const int32_t *p = chunk;
- unsigned num_frames = size / sizeof(*p) / fd->num_streams;
- if (num_frames > fd->period_size - fd->buffer_position)
- num_frames = fd->period_size - fd->buffer_position;
-
- for (unsigned i = num_frames; i > 0; --i) {
- for (int stream = 0; stream < fd->num_streams; ++stream)
- fd->streams[stream].buffer[fd->buffer_position] =
- *p++ / (float)(1 << 23);
- ++fd->buffer_position;
- }
-
- /* if buffer full, transfer to device */
-
- if (fd->buffer_position >= fd->period_size &&
- /* libffado documentation says this function returns -1 on
- error, but that is a lie - it returns a boolean value,
- and "false" means error */
- !ffado_streaming_transfer_playback_buffers(fd->dev)) {
- g_set_error(error_r, ffado_output_quark(), 0,
- "ffado_streaming_transfer_playback_buffers() failed");
- return 0;
- }
-
- return num_frames * sizeof(*p) * fd->num_streams;
-}
-
-const struct audio_output_plugin ffado_output_plugin = {
- .name = "ffado",
- .init = ffado_init,
- .finish = ffado_finish,
- .open = ffado_open,
- .close = ffado_close,
- .play = ffado_play,
-};
diff --git a/src/output/ffado_output_plugin.h b/src/output/ffado_output_plugin.h
deleted file mode 100644
index 4dde01859..000000000
--- a/src/output/ffado_output_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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_FFADO_OUTPUT_PLUGIN_H
-#define MPD_FFADO_OUTPUT_PLUGIN_H
-
-extern const struct audio_output_plugin ffado_output_plugin;
-
-#endif
diff --git a/src/output/fifo_output_plugin.c b/src/output/fifo_output_plugin.c
deleted file mode 100644
index 022be0b4a..000000000
--- a/src/output/fifo_output_plugin.c
+++ /dev/null
@@ -1,315 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "fifo_output_plugin.h"
-#include "output_api.h"
-#include "utils.h"
-#include "timer.h"
-#include "fd_util.h"
-#include "open.h"
-
-#include <glib.h>
-
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <errno.h>
-#include <string.h>
-#include <unistd.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "fifo"
-
-#define FIFO_BUFFER_SIZE 65536 /* pipe capacity on Linux >= 2.6.11 */
-
-struct fifo_data {
- struct audio_output base;
-
- char *path;
- int input;
- int output;
- bool created;
- struct timer *timer;
-};
-
-/**
- * The quark used for GError.domain.
- */
-static inline GQuark
-fifo_output_quark(void)
-{
- return g_quark_from_static_string("fifo_output");
-}
-
-static struct fifo_data *fifo_data_new(void)
-{
- struct fifo_data *ret;
-
- ret = g_new(struct fifo_data, 1);
-
- ret->path = NULL;
- ret->input = -1;
- ret->output = -1;
- ret->created = false;
-
- return ret;
-}
-
-static void fifo_data_free(struct fifo_data *fd)
-{
- g_free(fd->path);
- g_free(fd);
-}
-
-static void fifo_delete(struct fifo_data *fd)
-{
- g_debug("Removing FIFO \"%s\"", fd->path);
-
- if (unlink(fd->path) < 0) {
- g_warning("Could not remove FIFO \"%s\": %s",
- fd->path, g_strerror(errno));
- return;
- }
-
- fd->created = false;
-}
-
-static void
-fifo_close(struct fifo_data *fd)
-{
- struct stat st;
-
- if (fd->input >= 0) {
- close(fd->input);
- fd->input = -1;
- }
-
- if (fd->output >= 0) {
- close(fd->output);
- fd->output = -1;
- }
-
- if (fd->created && (stat(fd->path, &st) == 0))
- fifo_delete(fd);
-}
-
-static bool
-fifo_make(struct fifo_data *fd, GError **error)
-{
- if (mkfifo(fd->path, 0666) < 0) {
- g_set_error(error, fifo_output_quark(), errno,
- "Couldn't create FIFO \"%s\": %s",
- fd->path, g_strerror(errno));
- return false;
- }
-
- fd->created = true;
-
- return true;
-}
-
-static bool
-fifo_check(struct fifo_data *fd, GError **error)
-{
- struct stat st;
-
- if (stat(fd->path, &st) < 0) {
- if (errno == ENOENT) {
- /* Path doesn't exist */
- return fifo_make(fd, error);
- }
-
- g_set_error(error, fifo_output_quark(), errno,
- "Failed to stat FIFO \"%s\": %s",
- fd->path, g_strerror(errno));
- return false;
- }
-
- if (!S_ISFIFO(st.st_mode)) {
- g_set_error(error, fifo_output_quark(), 0,
- "\"%s\" already exists, but is not a FIFO",
- fd->path);
- return false;
- }
-
- return true;
-}
-
-static bool
-fifo_open(struct fifo_data *fd, GError **error)
-{
- if (!fifo_check(fd, error))
- return false;
-
- fd->input = open_cloexec(fd->path, O_RDONLY|O_NONBLOCK|O_BINARY, 0);
- if (fd->input < 0) {
- g_set_error(error, fifo_output_quark(), errno,
- "Could not open FIFO \"%s\" for reading: %s",
- fd->path, g_strerror(errno));
- fifo_close(fd);
- return false;
- }
-
- fd->output = open_cloexec(fd->path, O_WRONLY|O_NONBLOCK|O_BINARY, 0);
- if (fd->output < 0) {
- g_set_error(error, fifo_output_quark(), errno,
- "Could not open FIFO \"%s\" for writing: %s",
- fd->path, g_strerror(errno));
- fifo_close(fd);
- return false;
- }
-
- return true;
-}
-
-static struct audio_output *
-fifo_output_init(const struct config_param *param,
- GError **error_r)
-{
- struct fifo_data *fd;
-
- GError *error = NULL;
- char *path = config_dup_block_path(param, "path", &error);
- if (!path) {
- if (error != NULL)
- g_propagate_error(error_r, error);
- else
- g_set_error(error_r, fifo_output_quark(), 0,
- "No \"path\" parameter specified");
- return NULL;
- }
-
- fd = fifo_data_new();
- fd->path = path;
-
- if (!ao_base_init(&fd->base, &fifo_output_plugin, param, error_r)) {
- fifo_data_free(fd);
- return NULL;
- }
-
- if (!fifo_open(fd, error_r)) {
- ao_base_finish(&fd->base);
- fifo_data_free(fd);
- return NULL;
- }
-
- return &fd->base;
-}
-
-static void
-fifo_output_finish(struct audio_output *ao)
-{
- struct fifo_data *fd = (struct fifo_data *)ao;
-
- fifo_close(fd);
- ao_base_finish(&fd->base);
- fifo_data_free(fd);
-}
-
-static bool
-fifo_output_open(struct audio_output *ao, struct audio_format *audio_format,
- G_GNUC_UNUSED GError **error)
-{
- struct fifo_data *fd = (struct fifo_data *)ao;
-
- fd->timer = timer_new(audio_format);
-
- return true;
-}
-
-static void
-fifo_output_close(struct audio_output *ao)
-{
- struct fifo_data *fd = (struct fifo_data *)ao;
-
- timer_free(fd->timer);
-}
-
-static void
-fifo_output_cancel(struct audio_output *ao)
-{
- struct fifo_data *fd = (struct fifo_data *)ao;
- char buf[FIFO_BUFFER_SIZE];
- int bytes = 1;
-
- timer_reset(fd->timer);
-
- while (bytes > 0 && errno != EINTR)
- bytes = read(fd->input, buf, FIFO_BUFFER_SIZE);
-
- if (bytes < 0 && errno != EAGAIN) {
- g_warning("Flush of FIFO \"%s\" failed: %s",
- fd->path, g_strerror(errno));
- }
-}
-
-static unsigned
-fifo_output_delay(struct audio_output *ao)
-{
- struct fifo_data *fd = (struct fifo_data *)ao;
-
- return fd->timer->started
- ? timer_delay(fd->timer)
- : 0;
-}
-
-static size_t
-fifo_output_play(struct audio_output *ao, const void *chunk, size_t size,
- GError **error)
-{
- struct fifo_data *fd = (struct fifo_data *)ao;
- ssize_t bytes;
-
- if (!fd->timer->started)
- timer_start(fd->timer);
- timer_add(fd->timer, size);
-
- while (true) {
- bytes = write(fd->output, chunk, size);
- if (bytes > 0)
- return (size_t)bytes;
-
- if (bytes < 0) {
- switch (errno) {
- case EAGAIN:
- /* The pipe is full, so empty it */
- fifo_output_cancel(&fd->base);
- continue;
- case EINTR:
- continue;
- }
-
- g_set_error(error, fifo_output_quark(), errno,
- "Failed to write to FIFO %s: %s",
- fd->path, g_strerror(errno));
- return 0;
- }
- }
-}
-
-const struct audio_output_plugin fifo_output_plugin = {
- .name = "fifo",
- .init = fifo_output_init,
- .finish = fifo_output_finish,
- .open = fifo_output_open,
- .close = fifo_output_close,
- .delay = fifo_output_delay,
- .play = fifo_output_play,
- .cancel = fifo_output_cancel,
-};
diff --git a/src/output/fifo_output_plugin.h b/src/output/fifo_output_plugin.h
deleted file mode 100644
index 85f7985e1..000000000
--- a/src/output/fifo_output_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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_FIFO_OUTPUT_PLUGIN_H
-#define MPD_FIFO_OUTPUT_PLUGIN_H
-
-extern const struct audio_output_plugin fifo_output_plugin;
-
-#endif
diff --git a/src/output/httpd_client.c b/src/output/httpd_client.c
deleted file mode 100644
index 72de90457..000000000
--- a/src/output/httpd_client.c
+++ /dev/null
@@ -1,764 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "httpd_client.h"
-#include "httpd_internal.h"
-#include "fifo_buffer.h"
-#include "page.h"
-#include "icy_server.h"
-#include "glib_socket.h"
-
-#include <stdbool.h>
-#include <assert.h>
-#include <string.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "httpd_output"
-
-struct httpd_client {
- /**
- * The httpd output object this client is connected to.
- */
- struct httpd_output *httpd;
-
- /**
- * The TCP socket.
- */
- GIOChannel *channel;
-
- /**
- * The GLib main loop source id for reading from the socket,
- * and to detect errors.
- */
- guint read_source_id;
-
- /**
- * The GLib main loop source id for writing to the socket. If
- * 0, then there is no event source currently (because there
- * are no queued pages).
- */
- guint write_source_id;
-
- /**
- * For buffered reading. This pointer is only valid while the
- * HTTP request is read.
- */
- struct fifo_buffer *input;
-
- /**
- * The current state of the client.
- */
- enum {
- /** reading the request line */
- REQUEST,
-
- /** reading the request headers */
- HEADERS,
-
- /** sending the HTTP response */
- RESPONSE,
- } state;
-
- /**
- * A queue of #page objects to be sent to the client.
- */
- GQueue *pages;
-
- /**
- * The #page which is currently being sent to the client.
- */
- struct page *current_page;
-
- /**
- * The amount of bytes which were already sent from
- * #current_page.
- */
- size_t current_position;
-
- /**
- * If DLNA streaming was an option.
- */
- bool dlna_streaming_requested;
-
- /* ICY */
-
- /**
- * Do we support sending Icy-Metadata to the client? This is
- * disabled if the httpd audio output uses encoder tags.
- */
- bool metadata_supported;
-
- /**
- * If we should sent icy metadata.
- */
- bool metadata_requested;
-
- /**
- * If the current metadata was already sent to the client.
- */
- bool metadata_sent;
-
- /**
- * The amount of streaming data between each metadata block
- */
- guint metaint;
-
- /**
- * The metadata as #page which is currently being sent to the client.
- */
- struct page *metadata;
-
- /*
- * The amount of bytes which were already sent from the metadata.
- */
- size_t metadata_current_position;
-
- /**
- * The amount of streaming data sent to the client
- * since the last icy information was sent.
- */
- guint metadata_fill;
-};
-
-static void
-httpd_client_unref_page(gpointer data, G_GNUC_UNUSED gpointer user_data)
-{
- struct page *page = data;
-
- page_unref(page);
-}
-
-void
-httpd_client_free(struct httpd_client *client)
-{
- assert(client != NULL);
-
- if (client->state == RESPONSE) {
- if (client->write_source_id != 0)
- g_source_remove(client->write_source_id);
-
- if (client->current_page != NULL)
- page_unref(client->current_page);
-
- g_queue_foreach(client->pages, httpd_client_unref_page, NULL);
- g_queue_free(client->pages);
- } else
- fifo_buffer_free(client->input);
-
- if (client->metadata)
- page_unref (client->metadata);
-
- g_source_remove(client->read_source_id);
- g_io_channel_unref(client->channel);
- g_free(client);
-}
-
-/**
- * Frees the client and removes it from the server's client list.
- */
-static void
-httpd_client_close(struct httpd_client *client)
-{
- assert(client != NULL);
-
- httpd_output_remove_client(client->httpd, client);
- httpd_client_free(client);
-}
-
-/**
- * Switch the client to the "RESPONSE" state.
- */
-static void
-httpd_client_begin_response(struct httpd_client *client)
-{
- assert(client != NULL);
- assert(client->state != RESPONSE);
-
- client->state = RESPONSE;
- client->write_source_id = 0;
- client->pages = g_queue_new();
- client->current_page = NULL;
-
- httpd_output_send_header(client->httpd, client);
-}
-
-/**
- * Handle a line of the HTTP request.
- */
-static bool
-httpd_client_handle_line(struct httpd_client *client, const char *line)
-{
- assert(client->state != RESPONSE);
-
- if (client->state == REQUEST) {
- if (strncmp(line, "GET /", 5) != 0) {
- /* only GET is supported */
- g_warning("malformed request line from client");
- return false;
- }
-
- line = strchr(line + 5, ' ');
- if (line == NULL || strncmp(line + 1, "HTTP/", 5) != 0) {
- /* HTTP/0.9 without request headers */
- httpd_client_begin_response(client);
- return true;
- }
-
- /* after the request line, request headers follow */
- client->state = HEADERS;
- return true;
- } else {
- if (*line == 0) {
- /* empty line: request is finished */
- httpd_client_begin_response(client);
- return true;
- }
-
- if (g_ascii_strncasecmp(line, "Icy-MetaData: 1", 15) == 0) {
- /* Send icy metadata */
- client->metadata_requested =
- client->metadata_supported;
- return true;
- }
-
- if (g_ascii_strncasecmp(line, "transferMode.dlna.org: Streaming", 32) == 0) {
- /* Send as dlna */
- client->dlna_streaming_requested = true;
- /* metadata is not supported by dlna streaming, so disable it */
- client->metadata_supported = false;
- client->metadata_requested = false;
- return true;
- }
-
- /* expect more request headers */
- return true;
- }
-}
-
-/**
- * Check if a complete line of input is present in the input buffer,
- * and duplicates it. It is removed from the input buffer. The
- * return value has to be freed with g_free().
- */
-static char *
-httpd_client_read_line(struct httpd_client *client)
-{
- assert(client != NULL);
- assert(client->state != RESPONSE);
-
- const char *p, *newline;
- size_t length;
- char *line;
-
- p = fifo_buffer_read(client->input, &length);
- if (p == NULL)
- /* empty input buffer */
- return NULL;
-
- newline = memchr(p, '\n', length);
- if (newline == NULL)
- /* incomplete line */
- return NULL;
-
- line = g_strndup(p, newline - p);
- fifo_buffer_consume(client->input, newline - p + 1);
-
- /* remove trailing whitespace (e.g. '\r') */
- return g_strchomp(line);
-}
-
-/**
- * Sends the status line and response headers to the client.
- */
-static bool
-httpd_client_send_response(struct httpd_client *client)
-{
- char buffer[1024];
- GError *error = NULL;
- GIOStatus status;
- gsize bytes_written;
-
- assert(client != NULL);
- assert(client->state == RESPONSE);
-
- if (client->dlna_streaming_requested) {
- g_snprintf(buffer, sizeof(buffer),
- "HTTP/1.1 206 OK\r\n"
- "Content-Type: %s\r\n"
- "Content-Length: 10000\r\n"
- "Content-RangeX: 0-1000000/1000000\r\n"
- "transferMode.dlna.org: Streaming\r\n"
- "Accept-Ranges: bytes\r\n"
- "Connection: close\r\n"
- "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
- "contentFeatures.dlna.org: DLNA.ORG_OP=01;DLNA.ORG_CI=0\r\n"
- "\r\n",
- client->httpd->content_type);
-
- } else if (client->metadata_requested) {
- gchar *metadata_header;
-
- metadata_header = icy_server_metadata_header(
- client->httpd->name,
- client->httpd->genre,
- client->httpd->website,
- client->httpd->content_type,
- client->metaint);
-
- g_strlcpy(buffer, metadata_header, sizeof(buffer));
-
- g_free(metadata_header);
-
- } else { /* revert to a normal HTTP request */
- g_snprintf(buffer, sizeof(buffer),
- "HTTP/1.1 200 OK\r\n"
- "Content-Type: %s\r\n"
- "Connection: close\r\n"
- "Pragma: no-cache\r\n"
- "Cache-Control: no-cache, no-store\r\n"
- "\r\n",
- client->httpd->content_type);
- }
-
- status = g_io_channel_write_chars(client->channel,
- buffer, strlen(buffer),
- &bytes_written, &error);
-
- switch (status) {
- case G_IO_STATUS_NORMAL:
- case G_IO_STATUS_AGAIN:
- return true;
-
- case G_IO_STATUS_EOF:
- /* client has disconnected */
-
- httpd_client_close(client);
- return false;
-
- case G_IO_STATUS_ERROR:
- /* I/O error */
-
- g_warning("failed to write to client: %s", error->message);
- g_error_free(error);
-
- httpd_client_close(client);
- return false;
- }
-
- /* unreachable */
- httpd_client_close(client);
- return false;
-}
-
-/**
- * Data has been received from the client and it is appended to the
- * input buffer.
- */
-static bool
-httpd_client_received(struct httpd_client *client)
-{
- assert(client != NULL);
- assert(client->state != RESPONSE);
-
- char *line;
- bool success;
-
- while ((line = httpd_client_read_line(client)) != NULL) {
- success = httpd_client_handle_line(client, line);
- g_free(line);
- if (!success) {
- assert(client->state != RESPONSE);
- return false;
- }
-
- if (client->state == RESPONSE) {
- if (!fifo_buffer_is_empty(client->input)) {
- g_warning("unexpected input from client");
- return false;
- }
-
- fifo_buffer_free(client->input);
-
- return httpd_client_send_response(client);
- }
- }
-
- return true;
-}
-
-static bool
-httpd_client_read(struct httpd_client *client)
-{
- char *p;
- size_t max_length;
- GError *error = NULL;
- GIOStatus status;
- gsize bytes_read;
-
- if (client->state == RESPONSE) {
- /* the client has already sent the request, and he
- must not send more */
- char buffer[1];
-
- status = g_io_channel_read_chars(client->channel, buffer,
- sizeof(buffer), &bytes_read,
- NULL);
- if (status == G_IO_STATUS_NORMAL)
- g_warning("unexpected input from client");
-
- return false;
- }
-
- p = fifo_buffer_write(client->input, &max_length);
- if (p == NULL) {
- g_warning("buffer overflow");
- return false;
- }
-
- status = g_io_channel_read_chars(client->channel, p, max_length,
- &bytes_read, &error);
- switch (status) {
- case G_IO_STATUS_NORMAL:
- fifo_buffer_append(client->input, bytes_read);
- return httpd_client_received(client);
-
- case G_IO_STATUS_AGAIN:
- /* try again later, after select() */
- return true;
-
- case G_IO_STATUS_EOF:
- /* peer disconnected */
- return false;
-
- case G_IO_STATUS_ERROR:
- /* I/O error */
- g_warning("failed to read from client: %s",
- error->message);
- g_error_free(error);
- return false;
- }
-
- /* unreachable */
- return false;
-}
-
-static gboolean
-httpd_client_in_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition,
- gpointer data)
-{
- struct httpd_client *client = data;
- struct httpd_output *httpd = client->httpd;
- bool ret;
-
- g_mutex_lock(httpd->mutex);
-
- if (condition == G_IO_IN && httpd_client_read(client)) {
- ret = true;
- } else {
- httpd_client_close(client);
- ret = false;
- }
-
- g_mutex_unlock(httpd->mutex);
-
- return ret;
-}
-
-struct httpd_client *
-httpd_client_new(struct httpd_output *httpd, int fd, bool metadata_supported)
-{
- struct httpd_client *client = g_new(struct httpd_client, 1);
-
- client->httpd = httpd;
-
- client->channel = g_io_channel_new_socket(fd);
-
- /* GLib is responsible for closing the file descriptor */
- g_io_channel_set_close_on_unref(client->channel, true);
- /* NULL encoding means the stream is binary safe */
- g_io_channel_set_encoding(client->channel, NULL, NULL);
- /* we prefer to do buffering */
- g_io_channel_set_buffered(client->channel, false);
-
- client->read_source_id = g_io_add_watch(client->channel,
- G_IO_IN|G_IO_ERR|G_IO_HUP,
- httpd_client_in_event, client);
-
- client->input = fifo_buffer_new(4096);
- client->state = REQUEST;
-
- client->dlna_streaming_requested = false;
- client->metadata_supported = metadata_supported;
- client->metadata_requested = false;
- client->metadata_sent = true;
- client->metaint = 8192; /*TODO: just a std value */
- client->metadata = NULL;
- client->metadata_current_position = 0;
- client->metadata_fill = 0;
-
- return client;
-}
-
-static void
-httpd_client_add_page_size(gpointer data, gpointer user_data)
-{
- struct page *page = data;
- size_t *size = user_data;
-
- *size += page->size;
-}
-
-size_t
-httpd_client_queue_size(const struct httpd_client *client)
-{
- size_t size = 0;
-
- if (client->state != RESPONSE)
- return 0;
-
- g_queue_foreach(client->pages, httpd_client_add_page_size, &size);
- return size;
-}
-
-void
-httpd_client_cancel(struct httpd_client *client)
-{
- if (client->state != RESPONSE)
- return;
-
- g_queue_foreach(client->pages, httpd_client_unref_page, NULL);
- g_queue_clear(client->pages);
-
- if (client->write_source_id != 0 && client->current_page == NULL) {
- g_source_remove(client->write_source_id);
- client->write_source_id = 0;
- }
-}
-
-static GIOStatus
-write_page_to_channel(GIOChannel *channel,
- const struct page *page, size_t position,
- gsize *bytes_written_r, GError **error)
-{
- assert(channel != NULL);
- assert(page != NULL);
- assert(position < page->size);
-
- return g_io_channel_write_chars(channel,
- (const gchar*)page->data + position,
- page->size - position,
- bytes_written_r, error);
-}
-
-static GIOStatus
-write_n_bytes_to_channel(GIOChannel *channel, const struct page *page,
- size_t position, gint n,
- gsize *bytes_written_r, GError **error)
-{
- GIOStatus status;
-
- assert(channel != NULL);
- assert(page != NULL);
- assert(position < page->size);
-
- if (n == -1) {
- status = write_page_to_channel (channel, page, position,
- bytes_written_r, error);
- } else {
- status = g_io_channel_write_chars(channel,
- (const gchar*)page->data + position,
- n, bytes_written_r, error);
- }
-
- return status;
-}
-
-static gint
-bytes_left_till_metadata (struct httpd_client *client)
-{
- assert(client != NULL);
-
- if (client->metadata_requested &&
- client->current_page->size - client->current_position
- > client->metaint - client->metadata_fill)
- return client->metaint - client->metadata_fill;
-
- return -1;
-}
-
-static gboolean
-httpd_client_out_event(GIOChannel *source,
- G_GNUC_UNUSED GIOCondition condition, gpointer data)
-{
- struct httpd_client *client = data;
- struct httpd_output *httpd = client->httpd;
- GError *error = NULL;
- GIOStatus status;
- gsize bytes_written;
- gint bytes_to_write;
-
- g_mutex_lock(httpd->mutex);
-
- assert(condition == G_IO_OUT);
- assert(client->state == RESPONSE);
-
- if (client->write_source_id == 0) {
- /* another thread has removed the event source while
- this thread was waiting for httpd->mutex */
- g_mutex_unlock(httpd->mutex);
- return false;
- }
-
- if (client->current_page == NULL) {
- client->current_page = g_queue_pop_head(client->pages);
- client->current_position = 0;
- }
-
- bytes_to_write = bytes_left_till_metadata(client);
-
- if (bytes_to_write == 0) {
- gint metadata_to_write;
-
- metadata_to_write = client->metadata_current_position;
-
- if (!client->metadata_sent) {
- status = write_page_to_channel(source,
- client->metadata,
- metadata_to_write,
- &bytes_written, &error);
-
- client->metadata_current_position += bytes_written;
-
- if (client->metadata->size
- - client->metadata_current_position == 0) {
- client->metadata_fill = 0;
- client->metadata_current_position = 0;
- client->metadata_sent = true;
- }
- } else {
- struct page *empty_meta;
- guchar empty_data = 0;
-
- empty_meta = page_new_copy(&empty_data, 1);
-
- status = write_page_to_channel(source,
- empty_meta,
- metadata_to_write,
- &bytes_written, &error);
-
- client->metadata_current_position += bytes_written;
-
- if (empty_meta->size
- - client->metadata_current_position == 0) {
- client->metadata_fill = 0;
- client->metadata_current_position = 0;
- }
- }
-
- bytes_written = 0;
- } else {
- status = write_n_bytes_to_channel(source, client->current_page,
- client->current_position, bytes_to_write,
- &bytes_written, &error);
- }
-
- switch (status) {
- case G_IO_STATUS_NORMAL:
- client->current_position += bytes_written;
- assert(client->current_position <= client->current_page->size);
-
- if (client->metadata_requested)
- client->metadata_fill += bytes_written;
-
- if (client->current_position >= client->current_page->size) {
- page_unref(client->current_page);
- client->current_page = NULL;
-
- if (g_queue_is_empty(client->pages)) {
- /* all pages are sent: remove the
- event source */
- client->write_source_id = 0;
-
- g_mutex_unlock(httpd->mutex);
- return false;
- }
- }
-
- g_mutex_unlock(httpd->mutex);
- return true;
-
- case G_IO_STATUS_AGAIN:
- g_mutex_unlock(httpd->mutex);
- return true;
-
- case G_IO_STATUS_EOF:
- /* client has disconnected */
-
- httpd_client_close(client);
- g_mutex_unlock(httpd->mutex);
- return false;
-
- case G_IO_STATUS_ERROR:
- /* I/O error */
-
- g_warning("failed to write to client: %s", error->message);
- g_error_free(error);
-
- httpd_client_close(client);
- g_mutex_unlock(httpd->mutex);
- return false;
- }
-
- /* unreachable */
- httpd_client_close(client);
- g_mutex_unlock(httpd->mutex);
- return false;
-}
-
-void
-httpd_client_send(struct httpd_client *client, struct page *page)
-{
- if (client->state != RESPONSE)
- /* the client is still writing the HTTP request */
- return;
-
- page_ref(page);
- g_queue_push_tail(client->pages, page);
-
- if (client->write_source_id == 0)
- client->write_source_id =
- g_io_add_watch(client->channel, G_IO_OUT,
- httpd_client_out_event, client);
-}
-
-void
-httpd_client_send_metadata(struct httpd_client *client, struct page *page)
-{
- if (client->metadata) {
- page_unref(client->metadata);
- client->metadata = NULL;
- }
-
- g_return_if_fail (page);
-
- page_ref(page);
- client->metadata = page;
- client->metadata_sent = false;
-}
diff --git a/src/output/httpd_client.h b/src/output/httpd_client.h
deleted file mode 100644
index 739163f42..000000000
--- a/src/output/httpd_client.h
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * 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_OUTPUT_HTTPD_CLIENT_H
-#define MPD_OUTPUT_HTTPD_CLIENT_H
-
-#include <glib.h>
-
-#include <stdbool.h>
-
-struct httpd_client;
-struct httpd_output;
-struct page;
-
-/**
- * Creates a new #httpd_client object
- *
- * @param httpd the HTTP output device
- * @param fd the socket file descriptor
- */
-struct httpd_client *
-httpd_client_new(struct httpd_output *httpd, int fd, bool metadata_supported);
-
-/**
- * Frees memory and resources allocated by the #httpd_client object.
- * This does not remove it from the #httpd_output object.
- */
-void
-httpd_client_free(struct httpd_client *client);
-
-/**
- * Returns the total size of this client's page queue.
- */
-size_t
-httpd_client_queue_size(const struct httpd_client *client);
-
-/**
- * Clears the page queue.
- */
-void
-httpd_client_cancel(struct httpd_client *client);
-
-/**
- * Appends a page to the client's queue.
- */
-void
-httpd_client_send(struct httpd_client *client, struct page *page);
-
-/**
- * Sends the passed metadata.
- */
-void
-httpd_client_send_metadata(struct httpd_client *client, struct page *page);
-
-#endif
diff --git a/src/output/httpd_internal.h b/src/output/httpd_internal.h
deleted file mode 100644
index 5dcb8ab9b..000000000
--- a/src/output/httpd_internal.h
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * 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.
- */
-
-/** \file
- *
- * Internal declarations for the "httpd" audio output plugin.
- */
-
-#ifndef MPD_OUTPUT_HTTPD_INTERNAL_H
-#define MPD_OUTPUT_HTTPD_INTERNAL_H
-
-#include "output_internal.h"
-#include "timer.h"
-
-#include <glib.h>
-
-#include <stdbool.h>
-
-struct httpd_client;
-
-struct httpd_output {
- struct audio_output base;
-
- /**
- * True if the audio output is open and accepts client
- * connections.
- */
- bool open;
-
- /**
- * The configured encoder plugin.
- */
- struct encoder *encoder;
-
- /**
- * Number of bytes which were fed into the encoder, without
- * ever receiving new output. This is used to estimate
- * whether MPD should manually flush the encoder, to avoid
- * buffer underruns in the client.
- */
- size_t unflushed_input;
-
- /**
- * The MIME type produced by the #encoder.
- */
- const char *content_type;
-
- /**
- * This mutex protects the listener socket and the client
- * list.
- */
- GMutex *mutex;
-
- /**
- * A #timer object to synchronize this output with the
- * wallclock.
- */
- struct timer *timer;
-
- /**
- * The listener socket.
- */
- struct server_socket *server_socket;
-
- /**
- * The header page, which is sent to every client on connect.
- */
- struct page *header;
-
- /**
- * The metadata, which is sent to every client.
- */
- struct page *metadata;
-
- /**
- * The configured name.
- */
- char const *name;
- /**
- * The configured genre.
- */
- char const *genre;
- /**
- * The configured website address.
- */
- char const *website;
-
- /**
- * A linked list containing all clients which are currently
- * connected.
- */
- GList *clients;
-
- /**
- * A temporary buffer for the httpd_output_read_page()
- * function.
- */
- char buffer[32768];
-
- /**
- * The maximum and current number of clients connected
- * at the same time.
- */
- guint clients_max, clients_cnt;
-};
-
-/**
- * Removes a client from the httpd_output.clients linked list.
- */
-void
-httpd_output_remove_client(struct httpd_output *httpd,
- struct httpd_client *client);
-
-/**
- * Sends the encoder header to the client. This is called right after
- * the response headers have been sent.
- */
-void
-httpd_output_send_header(struct httpd_output *httpd,
- struct httpd_client *client);
-
-#endif
diff --git a/src/output/httpd_output_plugin.c b/src/output/httpd_output_plugin.c
deleted file mode 100644
index 1d730df7f..000000000
--- a/src/output/httpd_output_plugin.c
+++ /dev/null
@@ -1,623 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "httpd_output_plugin.h"
-#include "httpd_internal.h"
-#include "httpd_client.h"
-#include "output_api.h"
-#include "encoder_plugin.h"
-#include "encoder_list.h"
-#include "resolver.h"
-#include "page.h"
-#include "icy_server.h"
-#include "fd_util.h"
-#include "server_socket.h"
-
-#include <assert.h>
-
-#include <sys/types.h>
-#include <unistd.h>
-#include <errno.h>
-
-#ifdef HAVE_LIBWRAP
-#include <sys/socket.h> /* needed for AF_UNIX */
-#include <tcpd.h>
-#endif
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "httpd_output"
-
-/**
- * The quark used for GError.domain.
- */
-static inline GQuark
-httpd_output_quark(void)
-{
- return g_quark_from_static_string("httpd_output");
-}
-
-/**
- * Check whether there is at least one client.
- *
- * Caller must lock the mutex.
- */
-G_GNUC_PURE
-static bool
-httpd_output_has_clients(const struct httpd_output *httpd)
-{
- return httpd->clients != NULL;
-}
-
-/**
- * Check whether there is at least one client.
- */
-G_GNUC_PURE
-static bool
-httpd_output_lock_has_clients(const struct httpd_output *httpd)
-{
- g_mutex_lock(httpd->mutex);
- bool result = httpd_output_has_clients(httpd);
- g_mutex_unlock(httpd->mutex);
- return result;
-}
-
-static void
-httpd_listen_in_event(int fd, const struct sockaddr *address,
- size_t address_length, int uid, void *ctx);
-
-static bool
-httpd_output_bind(struct httpd_output *httpd, GError **error_r)
-{
- httpd->open = false;
-
- g_mutex_lock(httpd->mutex);
- bool success = server_socket_open(httpd->server_socket, error_r);
- g_mutex_unlock(httpd->mutex);
-
- return success;
-}
-
-static void
-httpd_output_unbind(struct httpd_output *httpd)
-{
- assert(!httpd->open);
-
- g_mutex_lock(httpd->mutex);
- server_socket_close(httpd->server_socket);
- g_mutex_unlock(httpd->mutex);
-}
-
-static struct audio_output *
-httpd_output_init(const struct config_param *param,
- GError **error)
-{
- struct httpd_output *httpd = g_new(struct httpd_output, 1);
- if (!ao_base_init(&httpd->base, &httpd_output_plugin, param, error)) {
- g_free(httpd);
- return NULL;
- }
-
- /* read configuration */
- httpd->name =
- config_get_block_string(param, "name", "Set name in config");
- httpd->genre =
- config_get_block_string(param, "genre", "Set genre in config");
- httpd->website =
- config_get_block_string(param, "website", "Set website in config");
-
- guint port = config_get_block_unsigned(param, "port", 8000);
-
- const char *encoder_name =
- config_get_block_string(param, "encoder", "vorbis");
- const struct encoder_plugin *encoder_plugin =
- encoder_plugin_get(encoder_name);
- if (encoder_plugin == NULL) {
- g_set_error(error, httpd_output_quark(), 0,
- "No such encoder: %s", encoder_name);
- ao_base_finish(&httpd->base);
- g_free(httpd);
- return NULL;
- }
-
- httpd->clients_max = config_get_block_unsigned(param,"max_clients", 0);
-
- /* set up bind_to_address */
-
- httpd->server_socket = server_socket_new(httpd_listen_in_event, httpd);
-
- const char *bind_to_address =
- config_get_block_string(param, "bind_to_address", NULL);
- bool success = bind_to_address != NULL &&
- strcmp(bind_to_address, "any") != 0
- ? server_socket_add_host(httpd->server_socket, bind_to_address,
- port, error)
- : server_socket_add_port(httpd->server_socket, port, error);
- if (!success) {
- ao_base_finish(&httpd->base);
- g_free(httpd);
- return NULL;
- }
-
- /* initialize metadata */
- httpd->metadata = NULL;
- httpd->unflushed_input = 0;
-
- /* initialize encoder */
-
- httpd->encoder = encoder_init(encoder_plugin, param, error);
- if (httpd->encoder == NULL) {
- ao_base_finish(&httpd->base);
- g_free(httpd);
- return NULL;
- }
-
- /* determine content type */
- httpd->content_type = encoder_get_mime_type(httpd->encoder);
- if (httpd->content_type == NULL) {
- httpd->content_type = "application/octet-stream";
- }
-
- httpd->mutex = g_mutex_new();
-
- return &httpd->base;
-}
-
-static void
-httpd_output_finish(struct audio_output *ao)
-{
- struct httpd_output *httpd = (struct httpd_output *)ao;
-
- if (httpd->metadata)
- page_unref(httpd->metadata);
-
- encoder_finish(httpd->encoder);
- server_socket_free(httpd->server_socket);
- g_mutex_free(httpd->mutex);
- ao_base_finish(&httpd->base);
- g_free(httpd);
-}
-
-/**
- * Creates a new #httpd_client object and adds it into the
- * httpd_output.clients linked list.
- */
-static void
-httpd_client_add(struct httpd_output *httpd, int fd)
-{
- struct httpd_client *client =
- httpd_client_new(httpd, fd,
- httpd->encoder->plugin->tag == NULL);
-
- httpd->clients = g_list_prepend(httpd->clients, client);
- httpd->clients_cnt++;
-
- /* pass metadata to client */
- if (httpd->metadata)
- httpd_client_send_metadata(client, httpd->metadata);
-}
-
-static void
-httpd_listen_in_event(int fd, const struct sockaddr *address,
- size_t address_length, G_GNUC_UNUSED int uid, void *ctx)
-{
- struct httpd_output *httpd = ctx;
-
- /* the listener socket has become readable - a client has
- connected */
-
-#ifdef HAVE_LIBWRAP
- if (address->sa_family != AF_UNIX) {
- char *hostaddr = sockaddr_to_string(address, address_length, NULL);
- const char *progname = g_get_prgname();
-
- struct request_info req;
- request_init(&req, RQ_FILE, fd, RQ_DAEMON, progname, 0);
-
- fromhost(&req);
-
- if (!hosts_access(&req)) {
- /* tcp wrappers says no */
- g_warning("libwrap refused connection (libwrap=%s) from %s",
- progname, hostaddr);
- g_free(hostaddr);
- close_socket(fd);
- g_mutex_unlock(httpd->mutex);
- return;
- }
-
- g_free(hostaddr);
- }
-#else
- (void)address;
- (void)address_length;
-#endif /* HAVE_WRAP */
-
- g_mutex_lock(httpd->mutex);
-
- if (fd >= 0) {
- /* can we allow additional client */
- if (httpd->open &&
- (httpd->clients_max == 0 ||
- httpd->clients_cnt < httpd->clients_max))
- httpd_client_add(httpd, fd);
- else
- close_socket(fd);
- } else if (fd < 0 && errno != EINTR) {
- g_warning("accept() failed: %s", g_strerror(errno));
- }
-
- g_mutex_unlock(httpd->mutex);
-}
-
-/**
- * Reads data from the encoder (as much as available) and returns it
- * as a new #page object.
- */
-static struct page *
-httpd_output_read_page(struct httpd_output *httpd)
-{
- if (httpd->unflushed_input >= 65536) {
- /* we have fed a lot of input into the encoder, but it
- didn't give anything back yet - flush now to avoid
- buffer underruns */
- encoder_flush(httpd->encoder, NULL);
- httpd->unflushed_input = 0;
- }
-
- size_t size = 0;
- do {
- size_t nbytes = encoder_read(httpd->encoder,
- httpd->buffer + size,
- sizeof(httpd->buffer) - size);
- if (nbytes == 0)
- break;
-
- httpd->unflushed_input = 0;
-
- size += nbytes;
- } while (size < sizeof(httpd->buffer));
-
- if (size == 0)
- return NULL;
-
- return page_new_copy(httpd->buffer, size);
-}
-
-static bool
-httpd_output_encoder_open(struct httpd_output *httpd,
- struct audio_format *audio_format,
- GError **error)
-{
- if (!encoder_open(httpd->encoder, audio_format, error))
- return false;
-
- /* we have to remember the encoder header, i.e. the first
- bytes of encoder output after opening it, because it has to
- be sent to every new client */
- httpd->header = httpd_output_read_page(httpd);
-
- httpd->unflushed_input = 0;
-
- return true;
-}
-
-static bool
-httpd_output_enable(struct audio_output *ao, GError **error_r)
-{
- struct httpd_output *httpd = (struct httpd_output *)ao;
-
- return httpd_output_bind(httpd, error_r);
-}
-
-static void
-httpd_output_disable(struct audio_output *ao)
-{
- struct httpd_output *httpd = (struct httpd_output *)ao;
-
- httpd_output_unbind(httpd);
-}
-
-static bool
-httpd_output_open(struct audio_output *ao, struct audio_format *audio_format,
- GError **error)
-{
- struct httpd_output *httpd = (struct httpd_output *)ao;
-
- g_mutex_lock(httpd->mutex);
-
- /* open the encoder */
-
- if (!httpd_output_encoder_open(httpd, audio_format, error)) {
- g_mutex_unlock(httpd->mutex);
- return false;
- }
-
- /* initialize other attributes */
-
- httpd->clients = NULL;
- httpd->clients_cnt = 0;
- httpd->timer = timer_new(audio_format);
-
- httpd->open = true;
-
- g_mutex_unlock(httpd->mutex);
- return true;
-}
-
-static void
-httpd_client_delete(gpointer data, G_GNUC_UNUSED gpointer user_data)
-{
- struct httpd_client *client = data;
-
- httpd_client_free(client);
-}
-
-static void
-httpd_output_close(struct audio_output *ao)
-{
- struct httpd_output *httpd = (struct httpd_output *)ao;
-
- g_mutex_lock(httpd->mutex);
-
- httpd->open = false;
-
- timer_free(httpd->timer);
-
- g_list_foreach(httpd->clients, httpd_client_delete, NULL);
- g_list_free(httpd->clients);
-
- if (httpd->header != NULL)
- page_unref(httpd->header);
-
- encoder_close(httpd->encoder);
-
- g_mutex_unlock(httpd->mutex);
-}
-
-void
-httpd_output_remove_client(struct httpd_output *httpd,
- struct httpd_client *client)
-{
- assert(httpd != NULL);
- assert(client != NULL);
-
- httpd->clients = g_list_remove(httpd->clients, client);
- httpd->clients_cnt--;
-}
-
-void
-httpd_output_send_header(struct httpd_output *httpd,
- struct httpd_client *client)
-{
- if (httpd->header != NULL)
- httpd_client_send(client, httpd->header);
-}
-
-static unsigned
-httpd_output_delay(struct audio_output *ao)
-{
- struct httpd_output *httpd = (struct httpd_output *)ao;
-
- if (!httpd_output_lock_has_clients(httpd) && httpd->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 */
- timer_reset(httpd->timer);
-
- /* some arbitrary delay that is long enough to avoid
- consuming too much CPU, and short enough to notice
- new clients quickly enough */
- return 1000;
- }
-
- return httpd->timer->started
- ? timer_delay(httpd->timer)
- : 0;
-}
-
-static void
-httpd_client_check_queue(gpointer data, G_GNUC_UNUSED gpointer user_data)
-{
- struct httpd_client *client = data;
-
- if (httpd_client_queue_size(client) > 256 * 1024) {
- g_debug("client is too slow, flushing its queue");
- httpd_client_cancel(client);
- }
-}
-
-static void
-httpd_client_send_page(gpointer data, gpointer user_data)
-{
- struct httpd_client *client = data;
- struct page *page = user_data;
-
- httpd_client_send(client, page);
-}
-
-/**
- * Broadcasts a page struct to all clients.
- */
-static void
-httpd_output_broadcast_page(struct httpd_output *httpd, struct page *page)
-{
- assert(page != NULL);
-
- g_mutex_lock(httpd->mutex);
- g_list_foreach(httpd->clients, httpd_client_send_page, page);
- g_mutex_unlock(httpd->mutex);
-}
-
-/**
- * Broadcasts data from the encoder to all clients.
- */
-static void
-httpd_output_encoder_to_clients(struct httpd_output *httpd)
-{
- struct page *page;
-
- g_mutex_lock(httpd->mutex);
- g_list_foreach(httpd->clients, httpd_client_check_queue, NULL);
- g_mutex_unlock(httpd->mutex);
-
- while ((page = httpd_output_read_page(httpd)) != NULL) {
- httpd_output_broadcast_page(httpd, page);
- page_unref(page);
- }
-}
-
-static bool
-httpd_output_encode_and_play(struct httpd_output *httpd,
- const void *chunk, size_t size, GError **error)
-{
- if (!encoder_write(httpd->encoder, chunk, size, error))
- return false;
-
- httpd->unflushed_input += size;
-
- httpd_output_encoder_to_clients(httpd);
-
- return true;
-}
-
-static size_t
-httpd_output_play(struct audio_output *ao, const void *chunk, size_t size,
- GError **error_r)
-{
- struct httpd_output *httpd = (struct httpd_output *)ao;
-
- if (httpd_output_lock_has_clients(httpd)) {
- if (!httpd_output_encode_and_play(httpd, chunk, size, error_r))
- return 0;
- }
-
- if (!httpd->timer->started)
- timer_start(httpd->timer);
- timer_add(httpd->timer, size);
-
- return size;
-}
-
-static bool
-httpd_output_pause(struct audio_output *ao)
-{
- struct httpd_output *httpd = (struct httpd_output *)ao;
-
- if (httpd_output_lock_has_clients(httpd)) {
- static const char silence[1020];
- return httpd_output_play(ao, silence, sizeof(silence),
- NULL) > 0;
- } else {
- return true;
- }
-}
-
-static void
-httpd_send_metadata(gpointer data, gpointer user_data)
-{
- struct httpd_client *client = data;
- struct page *icy_metadata = user_data;
-
- httpd_client_send_metadata(client, icy_metadata);
-}
-
-static void
-httpd_output_tag(struct audio_output *ao, const struct tag *tag)
-{
- struct httpd_output *httpd = (struct httpd_output *)ao;
-
- assert(tag != NULL);
-
- if (httpd->encoder->plugin->tag != NULL) {
- /* embed encoder tags */
-
- /* flush the current stream, and end it */
-
- encoder_pre_tag(httpd->encoder, NULL);
- httpd_output_encoder_to_clients(httpd);
-
- /* send the tag to the encoder - which starts a new
- stream now */
-
- encoder_tag(httpd->encoder, tag, NULL);
-
- /* the first page generated by the encoder will now be
- used as the new "header" page, which is sent to all
- new clients */
-
- struct page *page = httpd_output_read_page(httpd);
- if (page != NULL) {
- if (httpd->header != NULL)
- page_unref(httpd->header);
- httpd->header = page;
- httpd_output_broadcast_page(httpd, page);
- }
- } else {
- /* use Icy-Metadata */
-
- if (httpd->metadata != NULL)
- page_unref (httpd->metadata);
-
- httpd->metadata =
- icy_server_metadata_page(tag, TAG_ALBUM,
- TAG_ARTIST, TAG_TITLE,
- TAG_NUM_OF_ITEM_TYPES);
- if (httpd->metadata != NULL) {
- g_mutex_lock(httpd->mutex);
- g_list_foreach(httpd->clients,
- httpd_send_metadata, httpd->metadata);
- g_mutex_unlock(httpd->mutex);
- }
- }
-}
-
-static void
-httpd_client_cancel_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
-{
- struct httpd_client *client = data;
-
- httpd_client_cancel(client);
-}
-
-static void
-httpd_output_cancel(struct audio_output *ao)
-{
- struct httpd_output *httpd = (struct httpd_output *)ao;
-
- g_mutex_lock(httpd->mutex);
- g_list_foreach(httpd->clients, httpd_client_cancel_callback, NULL);
- g_mutex_unlock(httpd->mutex);
-}
-
-const struct audio_output_plugin httpd_output_plugin = {
- .name = "httpd",
- .init = httpd_output_init,
- .finish = httpd_output_finish,
- .enable = httpd_output_enable,
- .disable = httpd_output_disable,
- .open = httpd_output_open,
- .close = httpd_output_close,
- .delay = httpd_output_delay,
- .send_tag = httpd_output_tag,
- .play = httpd_output_play,
- .pause = httpd_output_pause,
- .cancel = httpd_output_cancel,
-};
diff --git a/src/output/httpd_output_plugin.h b/src/output/httpd_output_plugin.h
deleted file mode 100644
index d0eb1533f..000000000
--- a/src/output/httpd_output_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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_HTTPD_OUTPUT_PLUGIN_H
-#define MPD_HTTPD_OUTPUT_PLUGIN_H
-
-extern const struct audio_output_plugin httpd_output_plugin;
-
-#endif
diff --git a/src/output/jack_output_plugin.c b/src/output/jack_output_plugin.c
deleted file mode 100644
index d5c8ca412..000000000
--- a/src/output/jack_output_plugin.c
+++ /dev/null
@@ -1,755 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "jack_output_plugin.h"
-#include "output_api.h"
-
-#include <assert.h>
-
-#include <glib.h>
-#include <jack/jack.h>
-#include <jack/types.h>
-#include <jack/ringbuffer.h>
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <sys/types.h>
-#include <unistd.h>
-#include <errno.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "jack"
-
-enum {
- MAX_PORTS = 16,
-};
-
-static const size_t jack_sample_size = sizeof(jack_default_audio_sample_t);
-
-struct jack_data {
- struct audio_output base;
-
- /**
- * libjack options passed to jack_client_open().
- */
- jack_options_t options;
-
- const char *name;
-
- const char *server_name;
-
- /* configuration */
-
- char *source_ports[MAX_PORTS];
- unsigned num_source_ports;
-
- char *destination_ports[MAX_PORTS];
- unsigned num_destination_ports;
-
- size_t ringbuffer_size;
-
- /* the current audio format */
- struct audio_format audio_format;
-
- /* jack library stuff */
- jack_port_t *ports[MAX_PORTS];
- jack_client_t *client;
- jack_ringbuffer_t *ringbuffer[MAX_PORTS];
-
- bool shutdown;
-
- /**
- * While this flag is set, the "process" callback generates
- * silence.
- */
- bool pause;
-};
-
-/**
- * The quark used for GError.domain.
- */
-static inline GQuark
-jack_output_quark(void)
-{
- return g_quark_from_static_string("jack_output");
-}
-
-/**
- * Determine the number of frames guaranteed to be available on all
- * channels.
- */
-static jack_nframes_t
-mpd_jack_available(const struct jack_data *jd)
-{
- size_t min = jack_ringbuffer_read_space(jd->ringbuffer[0]);
-
- for (unsigned i = 1; i < jd->audio_format.channels; ++i) {
- size_t current = jack_ringbuffer_read_space(jd->ringbuffer[i]);
- if (current < min)
- min = current;
- }
-
- assert(min % jack_sample_size == 0);
-
- return min / jack_sample_size;
-}
-
-static int
-mpd_jack_process(jack_nframes_t nframes, void *arg)
-{
- struct jack_data *jd = (struct jack_data *) arg;
- jack_default_audio_sample_t *out;
-
- if (nframes <= 0)
- return 0;
-
- if (jd->pause) {
- /* empty the ring buffers */
-
- const jack_nframes_t available = mpd_jack_available(jd);
- for (unsigned i = 0; i < jd->audio_format.channels; ++i)
- jack_ringbuffer_read_advance(jd->ringbuffer[i],
- available * jack_sample_size);
-
- /* generate silence while MPD is paused */
-
- for (unsigned i = 0; i < jd->audio_format.channels; ++i) {
- out = jack_port_get_buffer(jd->ports[i], nframes);
-
- for (jack_nframes_t f = 0; f < nframes; ++f)
- out[f] = 0.0;
- }
-
- return 0;
- }
-
- jack_nframes_t available = mpd_jack_available(jd);
- if (available > nframes)
- available = nframes;
-
- for (unsigned i = 0; i < jd->audio_format.channels; ++i) {
- out = jack_port_get_buffer(jd->ports[i], nframes);
- if (out == NULL)
- /* workaround for libjack1 bug: if the server
- connection fails, the process callback is
- invoked anyway, but unable to get a
- buffer */
- continue;
-
- jack_ringbuffer_read(jd->ringbuffer[i],
- (char *)out, available * jack_sample_size);
-
- for (jack_nframes_t f = available; f < nframes; ++f)
- /* ringbuffer underrun, fill with silence */
- out[f] = 0.0;
- }
-
- /* generate silence for the unused source ports */
-
- for (unsigned i = jd->audio_format.channels;
- i < jd->num_source_ports; ++i) {
- out = jack_port_get_buffer(jd->ports[i], nframes);
- if (out == NULL)
- /* workaround for libjack1 bug: if the server
- connection fails, the process callback is
- invoked anyway, but unable to get a
- buffer */
- continue;
-
- for (jack_nframes_t f = 0; f < nframes; ++f)
- out[f] = 0.0;
- }
-
- return 0;
-}
-
-static void
-mpd_jack_shutdown(void *arg)
-{
- struct jack_data *jd = (struct jack_data *) arg;
- jd->shutdown = true;
-}
-
-static void
-set_audioformat(struct jack_data *jd, struct audio_format *audio_format)
-{
- audio_format->sample_rate = jack_get_sample_rate(jd->client);
-
- if (jd->num_source_ports == 1)
- audio_format->channels = 1;
- else if (audio_format->channels > jd->num_source_ports)
- audio_format->channels = 2;
-
- if (audio_format->format != SAMPLE_FORMAT_S16 &&
- audio_format->format != SAMPLE_FORMAT_S24_P32)
- audio_format->format = SAMPLE_FORMAT_S24_P32;
-}
-
-static void
-mpd_jack_error(const char *msg)
-{
- g_warning("%s", msg);
-}
-
-#ifdef HAVE_JACK_SET_INFO_FUNCTION
-static void
-mpd_jack_info(const char *msg)
-{
- g_message("%s", msg);
-}
-#endif
-
-/**
- * Disconnect the JACK client.
- */
-static void
-mpd_jack_disconnect(struct jack_data *jd)
-{
- assert(jd != NULL);
- assert(jd->client != NULL);
-
- jack_deactivate(jd->client);
- jack_client_close(jd->client);
- jd->client = NULL;
-}
-
-/**
- * Connect the JACK client and performs some basic setup
- * (e.g. register callbacks).
- */
-static bool
-mpd_jack_connect(struct jack_data *jd, GError **error_r)
-{
- jack_status_t status;
-
- assert(jd != NULL);
-
- jd->shutdown = false;
-
- jd->client = jack_client_open(jd->name, jd->options, &status,
- jd->server_name);
- if (jd->client == NULL) {
- g_set_error(error_r, jack_output_quark(), 0,
- "Failed to connect to JACK server, status=%d",
- status);
- return false;
- }
-
- jack_set_process_callback(jd->client, mpd_jack_process, jd);
- jack_on_shutdown(jd->client, mpd_jack_shutdown, jd);
-
- for (unsigned i = 0; i < jd->num_source_ports; ++i) {
- jd->ports[i] = jack_port_register(jd->client,
- jd->source_ports[i],
- JACK_DEFAULT_AUDIO_TYPE,
- JackPortIsOutput, 0);
- if (jd->ports[i] == NULL) {
- g_set_error(error_r, jack_output_quark(), 0,
- "Cannot register output port \"%s\"",
- jd->source_ports[i]);
- mpd_jack_disconnect(jd);
- return false;
- }
- }
-
- return true;
-}
-
-static bool
-mpd_jack_test_default_device(void)
-{
- return true;
-}
-
-static unsigned
-parse_port_list(int line, const char *source, char **dest, GError **error_r)
-{
- char **list = g_strsplit(source, ",", 0);
- unsigned n = 0;
-
- for (n = 0; list[n] != NULL; ++n) {
- if (n >= MAX_PORTS) {
- g_set_error(error_r, jack_output_quark(), 0,
- "too many port names in line %d",
- line);
- return 0;
- }
-
- dest[n] = list[n];
- }
-
- g_free(list);
-
- if (n == 0) {
- g_set_error(error_r, jack_output_quark(), 0,
- "at least one port name expected in line %d",
- line);
- return 0;
- }
-
- return n;
-}
-
-static struct audio_output *
-mpd_jack_init(const struct config_param *param, GError **error_r)
-{
- struct jack_data *jd = g_new(struct jack_data, 1);
-
- if (!ao_base_init(&jd->base, &jack_output_plugin, param, error_r)) {
- g_free(jd);
- return NULL;
- }
-
- const char *value;
-
- jd->options = JackNullOption;
-
- jd->name = config_get_block_string(param, "client_name", NULL);
- if (jd->name != NULL)
- jd->options |= JackUseExactName;
- else
- /* if there's a no configured client name, we don't
- care about the JackUseExactName option */
- jd->name = "Music Player Daemon";
-
- jd->server_name = config_get_block_string(param, "server_name", NULL);
- if (jd->server_name != NULL)
- jd->options |= JackServerName;
-
- if (!config_get_block_bool(param, "autostart", false))
- jd->options |= JackNoStartServer;
-
- /* configure the source ports */
-
- value = config_get_block_string(param, "source_ports", "left,right");
- jd->num_source_ports = parse_port_list(param->line, value,
- jd->source_ports, error_r);
- if (jd->num_source_ports == 0)
- return NULL;
-
- /* configure the destination ports */
-
- value = config_get_block_string(param, "destination_ports", NULL);
- if (value == NULL) {
- /* compatibility with MPD < 0.16 */
- value = config_get_block_string(param, "ports", NULL);
- if (value != NULL)
- g_warning("deprecated option 'ports' in line %d",
- param->line);
- }
-
- if (value != NULL) {
- jd->num_destination_ports =
- parse_port_list(param->line, value,
- jd->destination_ports, error_r);
- if (jd->num_destination_ports == 0)
- return NULL;
- } else {
- jd->num_destination_ports = 0;
- }
-
- if (jd->num_destination_ports > 0 &&
- jd->num_destination_ports != jd->num_source_ports)
- g_warning("number of source ports (%u) mismatches the "
- "number of destination ports (%u) in line %d",
- jd->num_source_ports, jd->num_destination_ports,
- param->line);
-
- jd->ringbuffer_size =
- config_get_block_unsigned(param, "ringbuffer_size", 32768);
-
- jack_set_error_function(mpd_jack_error);
-
-#ifdef HAVE_JACK_SET_INFO_FUNCTION
- jack_set_info_function(mpd_jack_info);
-#endif
-
- return &jd->base;
-}
-
-static void
-mpd_jack_finish(struct audio_output *ao)
-{
- struct jack_data *jd = (struct jack_data *)ao;
-
- for (unsigned i = 0; i < jd->num_source_ports; ++i)
- g_free(jd->source_ports[i]);
-
- for (unsigned i = 0; i < jd->num_destination_ports; ++i)
- g_free(jd->destination_ports[i]);
-
- ao_base_finish(&jd->base);
- g_free(jd);
-}
-
-static bool
-mpd_jack_enable(struct audio_output *ao, GError **error_r)
-{
- struct jack_data *jd = (struct jack_data *)ao;
-
- for (unsigned i = 0; i < jd->num_source_ports; ++i)
- jd->ringbuffer[i] = NULL;
-
- return mpd_jack_connect(jd, error_r);
-}
-
-static void
-mpd_jack_disable(struct audio_output *ao)
-{
- struct jack_data *jd = (struct jack_data *)ao;
-
- if (jd->client != NULL)
- mpd_jack_disconnect(jd);
-
- for (unsigned i = 0; i < jd->num_source_ports; ++i) {
- if (jd->ringbuffer[i] != NULL) {
- jack_ringbuffer_free(jd->ringbuffer[i]);
- jd->ringbuffer[i] = NULL;
- }
- }
-}
-
-/**
- * Stops the playback on the JACK connection.
- */
-static void
-mpd_jack_stop(struct jack_data *jd)
-{
- assert(jd != NULL);
-
- if (jd->client == NULL)
- return;
-
- if (jd->shutdown)
- /* the connection has failed; close it */
- mpd_jack_disconnect(jd);
- else
- /* the connection is alive: just stop playback */
- jack_deactivate(jd->client);
-}
-
-static bool
-mpd_jack_start(struct jack_data *jd, GError **error_r)
-{
- const char *destination_ports[MAX_PORTS], **jports;
- const char *duplicate_port = NULL;
- unsigned num_destination_ports;
-
- assert(jd->client != NULL);
- assert(jd->audio_format.channels <= jd->num_source_ports);
-
- /* allocate the ring buffers on the first open(); these
- persist until MPD exits. It's too unsafe to delete them
- because we can never know when mpd_jack_process() gets
- called */
- for (unsigned i = 0; i < jd->num_source_ports; ++i) {
- if (jd->ringbuffer[i] == NULL)
- jd->ringbuffer[i] =
- jack_ringbuffer_create(jd->ringbuffer_size);
-
- /* clear the ring buffer to be sure that data from
- previous playbacks are gone */
- jack_ringbuffer_reset(jd->ringbuffer[i]);
- }
-
- if ( jack_activate(jd->client) ) {
- g_set_error(error_r, jack_output_quark(), 0,
- "cannot activate client");
- mpd_jack_stop(jd);
- return false;
- }
-
- if (jd->num_destination_ports == 0) {
- /* no output ports were configured - ask libjack for
- defaults */
- jports = jack_get_ports(jd->client, NULL, NULL,
- JackPortIsPhysical | JackPortIsInput);
- if (jports == NULL) {
- g_set_error(error_r, jack_output_quark(), 0,
- "no ports found");
- mpd_jack_stop(jd);
- return false;
- }
-
- assert(*jports != NULL);
-
- for (num_destination_ports = 0;
- num_destination_ports < MAX_PORTS &&
- jports[num_destination_ports] != NULL;
- ++num_destination_ports) {
- g_debug("destination_port[%u] = '%s'\n",
- num_destination_ports,
- jports[num_destination_ports]);
- destination_ports[num_destination_ports] =
- jports[num_destination_ports];
- }
- } else {
- /* use the configured output ports */
-
- num_destination_ports = jd->num_destination_ports;
- memcpy(destination_ports, jd->destination_ports,
- num_destination_ports * sizeof(*destination_ports));
-
- jports = NULL;
- }
-
- assert(num_destination_ports > 0);
-
- if (jd->audio_format.channels >= 2 && num_destination_ports == 1) {
- /* mix stereo signal on one speaker */
-
- while (num_destination_ports < jd->audio_format.channels)
- destination_ports[num_destination_ports++] =
- destination_ports[0];
- } else if (num_destination_ports > jd->audio_format.channels) {
- if (jd->audio_format.channels == 1 && num_destination_ports > 2) {
- /* mono input file: connect the one source
- channel to the both destination channels */
- duplicate_port = destination_ports[1];
- num_destination_ports = 1;
- } else
- /* connect only as many ports as we need */
- num_destination_ports = jd->audio_format.channels;
- }
-
- assert(num_destination_ports <= jd->num_source_ports);
-
- for (unsigned i = 0; i < num_destination_ports; ++i) {
- int ret;
-
- ret = jack_connect(jd->client, jack_port_name(jd->ports[i]),
- destination_ports[i]);
- if (ret != 0) {
- g_set_error(error_r, jack_output_quark(), 0,
- "Not a valid JACK port: %s",
- destination_ports[i]);
-
- if (jports != NULL)
- free(jports);
-
- mpd_jack_stop(jd);
- return false;
- }
- }
-
- if (duplicate_port != NULL) {
- /* mono input file: connect the one source channel to
- the both destination channels */
- int ret;
-
- ret = jack_connect(jd->client, jack_port_name(jd->ports[0]),
- duplicate_port);
- if (ret != 0) {
- g_set_error(error_r, jack_output_quark(), 0,
- "Not a valid JACK port: %s",
- duplicate_port);
-
- if (jports != NULL)
- free(jports);
-
- mpd_jack_stop(jd);
- return false;
- }
- }
-
- if (jports != NULL)
- free(jports);
-
- return true;
-}
-
-static bool
-mpd_jack_open(struct audio_output *ao, struct audio_format *audio_format,
- GError **error_r)
-{
- struct jack_data *jd = (struct jack_data *)ao;
-
- assert(jd != NULL);
-
- jd->pause = false;
-
- if (jd->client != NULL && jd->shutdown)
- mpd_jack_disconnect(jd);
-
- if (jd->client == NULL && !mpd_jack_connect(jd, error_r))
- return false;
-
- set_audioformat(jd, audio_format);
- jd->audio_format = *audio_format;
-
- if (!mpd_jack_start(jd, error_r))
- return false;
-
- return true;
-}
-
-static void
-mpd_jack_close(G_GNUC_UNUSED struct audio_output *ao)
-{
- struct jack_data *jd = (struct jack_data *)ao;
-
- mpd_jack_stop(jd);
-}
-
-static unsigned
-mpd_jack_delay(struct audio_output *ao)
-{
- struct jack_data *jd = (struct jack_data *)ao;
-
- return jd->base.pause && jd->pause && !jd->shutdown
- ? 1000
- : 0;
-}
-
-static inline jack_default_audio_sample_t
-sample_16_to_jack(int16_t sample)
-{
- return sample / (jack_default_audio_sample_t)(1 << (16 - 1));
-}
-
-static void
-mpd_jack_write_samples_16(struct jack_data *jd, const int16_t *src,
- unsigned num_samples)
-{
- jack_default_audio_sample_t sample;
- unsigned i;
-
- while (num_samples-- > 0) {
- for (i = 0; i < jd->audio_format.channels; ++i) {
- sample = sample_16_to_jack(*src++);
- jack_ringbuffer_write(jd->ringbuffer[i], (void*)&sample,
- sizeof(sample));
- }
- }
-}
-
-static inline jack_default_audio_sample_t
-sample_24_to_jack(int32_t sample)
-{
- return sample / (jack_default_audio_sample_t)(1 << (24 - 1));
-}
-
-static void
-mpd_jack_write_samples_24(struct jack_data *jd, const int32_t *src,
- unsigned num_samples)
-{
- jack_default_audio_sample_t sample;
- unsigned i;
-
- while (num_samples-- > 0) {
- for (i = 0; i < jd->audio_format.channels; ++i) {
- sample = sample_24_to_jack(*src++);
- jack_ringbuffer_write(jd->ringbuffer[i], (void*)&sample,
- sizeof(sample));
- }
- }
-}
-
-static void
-mpd_jack_write_samples(struct jack_data *jd, const void *src,
- unsigned num_samples)
-{
- switch (jd->audio_format.format) {
- case SAMPLE_FORMAT_S16:
- mpd_jack_write_samples_16(jd, (const int16_t*)src,
- num_samples);
- break;
-
- case SAMPLE_FORMAT_S24_P32:
- mpd_jack_write_samples_24(jd, (const int32_t*)src,
- num_samples);
- break;
-
- default:
- assert(false);
- }
-}
-
-static size_t
-mpd_jack_play(struct audio_output *ao, const void *chunk, size_t size,
- GError **error_r)
-{
- struct jack_data *jd = (struct jack_data *)ao;
- const size_t frame_size = audio_format_frame_size(&jd->audio_format);
- size_t space = 0, space1;
-
- jd->pause = false;
-
- assert(size % frame_size == 0);
- size /= frame_size;
-
- while (true) {
- if (jd->shutdown) {
- g_set_error(error_r, jack_output_quark(), 0,
- "Refusing to play, because "
- "there is no client thread");
- return 0;
- }
-
- space = jack_ringbuffer_write_space(jd->ringbuffer[0]);
- for (unsigned i = 1; i < jd->audio_format.channels; ++i) {
- space1 = jack_ringbuffer_write_space(jd->ringbuffer[i]);
- if (space > space1)
- /* send data symmetrically */
- space = space1;
- }
-
- if (space >= jack_sample_size)
- break;
-
- /* XXX do something more intelligent to
- synchronize */
- g_usleep(1000);
- }
-
- space /= jack_sample_size;
- if (space < size)
- size = space;
-
- mpd_jack_write_samples(jd, chunk, size);
- return size * frame_size;
-}
-
-static bool
-mpd_jack_pause(struct audio_output *ao)
-{
- struct jack_data *jd = (struct jack_data *)ao;
-
- if (jd->shutdown)
- return false;
-
- jd->pause = true;
-
- return true;
-}
-
-const struct audio_output_plugin jack_output_plugin = {
- .name = "jack",
- .test_default_device = mpd_jack_test_default_device,
- .init = mpd_jack_init,
- .finish = mpd_jack_finish,
- .enable = mpd_jack_enable,
- .disable = mpd_jack_disable,
- .open = mpd_jack_open,
- .delay = mpd_jack_delay,
- .play = mpd_jack_play,
- .pause = mpd_jack_pause,
- .close = mpd_jack_close,
-};
diff --git a/src/output/jack_output_plugin.h b/src/output/jack_output_plugin.h
deleted file mode 100644
index 2f94ae7dc..000000000
--- a/src/output/jack_output_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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_JACK_OUTPUT_PLUGIN_H
-#define MPD_JACK_OUTPUT_PLUGIN_H
-
-extern const struct audio_output_plugin jack_output_plugin;
-
-#endif
diff --git a/src/output/mvp_output_plugin.c b/src/output/mvp_output_plugin.c
deleted file mode 100644
index 37e0f7c93..000000000
--- a/src/output/mvp_output_plugin.c
+++ /dev/null
@@ -1,344 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * Media MVP audio output based on code from MVPMC project:
- * http://mvpmc.sourceforge.net/
- */
-
-#include "config.h"
-#include "mvp_output_plugin.h"
-#include "output_api.h"
-#include "fd_util.h"
-
-#include <glib.h>
-
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/ioctl.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <unistd.h>
-#include <stdlib.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "mvp"
-
-typedef struct {
- unsigned long dsp_status;
- unsigned long stream_decode_type;
- unsigned long sample_rate;
- unsigned long bit_rate;
- unsigned long raw[64 / sizeof(unsigned long)];
-} aud_status_t;
-
-#define MVP_SET_AUD_STOP _IOW('a',1,int)
-#define MVP_SET_AUD_PLAY _IOW('a',2,int)
-#define MVP_SET_AUD_PAUSE _IOW('a',3,int)
-#define MVP_SET_AUD_UNPAUSE _IOW('a',4,int)
-#define MVP_SET_AUD_SRC _IOW('a',5,int)
-#define MVP_SET_AUD_MUTE _IOW('a',6,int)
-#define MVP_SET_AUD_BYPASS _IOW('a',8,int)
-#define MVP_SET_AUD_CHANNEL _IOW('a',9,int)
-#define MVP_GET_AUD_STATUS _IOR('a',10,aud_status_t)
-#define MVP_SET_AUD_VOLUME _IOW('a',13,int)
-#define MVP_GET_AUD_VOLUME _IOR('a',14,int)
-#define MVP_SET_AUD_STREAMTYPE _IOW('a',15,int)
-#define MVP_SET_AUD_FORMAT _IOW('a',16,int)
-#define MVP_GET_AUD_SYNC _IOR('a',21,pts_sync_data_t*)
-#define MVP_SET_AUD_STC _IOW('a',22,long long int *)
-#define MVP_SET_AUD_SYNC _IOW('a',23,int)
-#define MVP_SET_AUD_END_STREAM _IOW('a',25,int)
-#define MVP_SET_AUD_RESET _IOW('a',26,int)
-#define MVP_SET_AUD_DAC_CLK _IOW('a',27,int)
-#define MVP_GET_AUD_REGS _IOW('a',28,aud_ctl_regs_t*)
-
-struct mvp_data {
- struct audio_output base;
-
- struct audio_format audio_format;
- int fd;
-};
-
-static const unsigned mvp_sample_rates[][3] = {
- {9, 8000, 32000},
- {10, 11025, 44100},
- {11, 12000, 48000},
- {1, 16000, 32000},
- {2, 22050, 44100},
- {3, 24000, 48000},
- {5, 32000, 32000},
- {0, 44100, 44100},
- {7, 48000, 48000},
- {13, 64000, 32000},
- {14, 88200, 44100},
- {15, 96000, 48000}
-};
-
-/**
- * The quark used for GError.domain.
- */
-static inline GQuark
-mvp_output_quark(void)
-{
- return g_quark_from_static_string("mvp_output");
-}
-
-/**
- * Translate a sample rate to a MVP sample rate.
- *
- * @param sample_rate the sample rate in Hz
- */
-static unsigned
-mvp_find_sample_rate(unsigned sample_rate)
-{
- for (unsigned i = 0; i < G_N_ELEMENTS(mvp_sample_rates); ++i)
- if (mvp_sample_rates[i][1] == sample_rate)
- return mvp_sample_rates[i][0];
-
- return (unsigned)-1;
-}
-
-static bool
-mvp_output_test_default_device(void)
-{
- int fd;
-
- fd = open_cloexec("/dev/adec_pcm", O_WRONLY, 0);
-
- if (fd >= 0) {
- close(fd);
- return true;
- }
-
- g_warning("Error opening PCM device \"/dev/adec_pcm\": %s\n",
- g_strerror(errno));
-
- return false;
-}
-
-static struct audio_output *
-mvp_output_init(G_GNUC_UNUSED const struct config_param *param, GError **error)
-{
- struct mvp_data *md = g_new(struct mvp_data, 1);
-
- if (!ao_base_init(&md->base, &mvp_output_plugin, param, error)) {
- g_free(md);
- return NULL;
- }
-
- md->fd = -1;
-
- return &md->base;
-}
-
-static void
-mvp_output_finish(struct audio_output *ao)
-{
- struct mvp_data *md = (struct mvp_data *)ao;
- ao_base_finish(&md->base);
- g_free(md);
-}
-
-static bool
-mvp_set_pcm_params(struct mvp_data *md, struct audio_format *audio_format,
- GError **error)
-{
- unsigned mix[5];
-
- switch (audio_format->channels) {
- case 1:
- mix[0] = 1;
- break;
-
- case 2:
- mix[0] = 0;
- break;
-
- default:
- g_debug("unsupported channel count %u - falling back to stereo",
- audio_format->channels);
- audio_format->channels = 2;
- mix[0] = 0;
- break;
- }
-
- /* 0,1=24bit(24) , 2,3=16bit */
- switch (audio_format->format) {
- case SAMPLE_FORMAT_S16:
- mix[1] = 2;
- break;
-
- case SAMPLE_FORMAT_S24_P32:
- mix[1] = 0;
- break;
-
- default:
- g_debug("unsupported sample format %s - falling back to 16 bit",
- sample_format_to_string(audio_format->format));
- audio_format->format = SAMPLE_FORMAT_S16;
- mix[1] = 2;
- break;
- }
-
- mix[3] = 0; /* stream type? */
- mix[4] = G_BYTE_ORDER == G_LITTLE_ENDIAN;
-
- /*
- * if there is an exact match for the frequency, use it.
- */
- mix[2] = mvp_find_sample_rate(audio_format->sample_rate);
- if (mix[2] == (unsigned)-1) {
- g_set_error(error, mvp_output_quark(), 0,
- "Can not find suitable output frequency for %u",
- audio_format->sample_rate);
- return false;
- }
-
- if (ioctl(md->fd, MVP_SET_AUD_FORMAT, &mix) < 0) {
- g_set_error(error, mvp_output_quark(), errno,
- "Can not set audio format");
- return false;
- }
-
- if (ioctl(md->fd, MVP_SET_AUD_SYNC, 2) != 0) {
- g_set_error(error, mvp_output_quark(), errno,
- "Can not set audio sync");
- return false;
- }
-
- if (ioctl(md->fd, MVP_SET_AUD_PLAY, 0) < 0) {
- g_set_error(error, mvp_output_quark(), errno,
- "Can not set audio play mode");
- return false;
- }
-
- return true;
-}
-
-static bool
-mvp_output_open(struct audio_output *ao, struct audio_format *audio_format,
- GError **error)
-{
- struct mvp_data *md = (struct mvp_data *)ao;
- long long int stc = 0;
- int mix[5] = { 0, 2, 7, 1, 0 };
- bool success;
-
- md->fd = open_cloexec("/dev/adec_pcm", O_RDWR | O_NONBLOCK, 0);
- if (md->fd < 0) {
- g_set_error(error, mvp_output_quark(), errno,
- "Error opening /dev/adec_pcm: %s",
- g_strerror(errno));
- return false;
- }
- if (ioctl(md->fd, MVP_SET_AUD_SRC, 1) < 0) {
- g_set_error(error, mvp_output_quark(), errno,
- "Error setting audio source: %s",
- g_strerror(errno));
- return false;
- }
- if (ioctl(md->fd, MVP_SET_AUD_STREAMTYPE, 0) < 0) {
- g_set_error(error, mvp_output_quark(), errno,
- "Error setting audio streamtype: %s",
- g_strerror(errno));
- return false;
- }
- if (ioctl(md->fd, MVP_SET_AUD_FORMAT, &mix) < 0) {
- g_set_error(error, mvp_output_quark(), errno,
- "Error setting audio format: %s",
- g_strerror(errno));
- return false;
- }
- ioctl(md->fd, MVP_SET_AUD_STC, &stc);
- if (ioctl(md->fd, MVP_SET_AUD_BYPASS, 1) < 0) {
- g_set_error(error, mvp_output_quark(), errno,
- "Error setting audio streamtype: %s",
- g_strerror(errno));
- return false;
- }
-
- success = mvp_set_pcm_params(md, audio_format, error);
- if (!success)
- return false;
-
- md->audio_format = *audio_format;
- return true;
-}
-
-static void mvp_output_close(struct audio_output *ao)
-{
- struct mvp_data *md = (struct mvp_data *)ao;
- if (md->fd >= 0)
- close(md->fd);
- md->fd = -1;
-}
-
-static void mvp_output_cancel(struct audio_output *ao)
-{
- struct mvp_data *md = (struct mvp_data *)ao;
- if (md->fd >= 0) {
- ioctl(md->fd, MVP_SET_AUD_RESET, 0x11);
- close(md->fd);
- md->fd = -1;
- }
-}
-
-static size_t
-mvp_output_play(struct audio_output *ao, const void *chunk, size_t size,
- GError **error)
-{
- struct mvp_data *md = (struct mvp_data *)ao;
- ssize_t ret;
-
- /* reopen the device since it was closed by dropBufferedAudio */
- if (md->fd < 0) {
- bool success;
-
- success = mvp_output_open(ao, &md->audio_format, error);
- if (!success)
- return 0;
- }
-
- while (true) {
- ret = write(md->fd, chunk, size);
- if (ret > 0)
- return (size_t)ret;
-
- if (ret < 0) {
- if (errno == EINTR)
- continue;
-
- g_set_error(error, mvp_output_quark(), errno,
- "Failed to write: %s", g_strerror(errno));
- return 0;
- }
- }
-}
-
-const struct audio_output_plugin mvp_output_plugin = {
- .name = "mvp",
- .test_default_device = mvp_output_test_default_device,
- .init = mvp_output_init,
- .finish = mvp_output_finish,
- .open = mvp_output_open,
- .close = mvp_output_close,
- .play = mvp_output_play,
- .cancel = mvp_output_cancel,
-};
diff --git a/src/output/mvp_output_plugin.h b/src/output/mvp_output_plugin.h
deleted file mode 100644
index e403de2b7..000000000
--- a/src/output/mvp_output_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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_MVP_OUTPUT_PLUGIN_H
-#define MPD_MVP_OUTPUT_PLUGIN_H
-
-extern const struct audio_output_plugin mvp_output_plugin;
-
-#endif
diff --git a/src/output/null_output_plugin.c b/src/output/null_output_plugin.c
deleted file mode 100644
index 9d7588fff..000000000
--- a/src/output/null_output_plugin.c
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "null_output_plugin.h"
-#include "output_api.h"
-#include "timer.h"
-
-#include <glib.h>
-
-#include <assert.h>
-
-struct null_data {
- struct audio_output base;
-
- bool sync;
-
- struct timer *timer;
-};
-
-static struct audio_output *
-null_init(const struct config_param *param, GError **error_r)
-{
- struct null_data *nd = g_new(struct null_data, 1);
-
- if (!ao_base_init(&nd->base, &null_output_plugin, param, error_r)) {
- g_free(nd);
- return NULL;
- }
-
- nd->sync = config_get_block_bool(param, "sync", true);
-
- return &nd->base;
-}
-
-static void
-null_finish(struct audio_output *ao)
-{
- struct null_data *nd = (struct null_data *)ao;
-
- ao_base_finish(&nd->base);
- g_free(nd);
-}
-
-static bool
-null_open(struct audio_output *ao, struct audio_format *audio_format,
- G_GNUC_UNUSED GError **error)
-{
- struct null_data *nd = (struct null_data *)ao;
-
- if (nd->sync)
- nd->timer = timer_new(audio_format);
-
- return true;
-}
-
-static void
-null_close(struct audio_output *ao)
-{
- struct null_data *nd = (struct null_data *)ao;
-
- if (nd->sync)
- timer_free(nd->timer);
-}
-
-static unsigned
-null_delay(struct audio_output *ao)
-{
- struct null_data *nd = (struct null_data *)ao;
-
- return nd->sync && nd->timer->started
- ? timer_delay(nd->timer)
- : 0;
-}
-
-static size_t
-null_play(struct audio_output *ao, G_GNUC_UNUSED const void *chunk, size_t size,
- G_GNUC_UNUSED GError **error)
-{
- struct null_data *nd = (struct null_data *)ao;
- struct timer *timer = nd->timer;
-
- if (!nd->sync)
- return size;
-
- if (!timer->started)
- timer_start(timer);
- timer_add(timer, size);
-
- return size;
-}
-
-static void
-null_cancel(struct audio_output *ao)
-{
- struct null_data *nd = (struct null_data *)ao;
-
- if (!nd->sync)
- return;
-
- timer_reset(nd->timer);
-}
-
-const struct audio_output_plugin null_output_plugin = {
- .name = "null",
- .init = null_init,
- .finish = null_finish,
- .open = null_open,
- .close = null_close,
- .delay = null_delay,
- .play = null_play,
- .cancel = null_cancel,
-};
diff --git a/src/output/null_output_plugin.h b/src/output/null_output_plugin.h
deleted file mode 100644
index 392bf0aa3..000000000
--- a/src/output/null_output_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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_NULL_OUTPUT_PLUGIN_H
-#define MPD_NULL_OUTPUT_PLUGIN_H
-
-extern const struct audio_output_plugin null_output_plugin;
-
-#endif
diff --git a/src/output/openal_output_plugin.c b/src/output/openal_output_plugin.c
deleted file mode 100644
index ebd35ef12..000000000
--- a/src/output/openal_output_plugin.c
+++ /dev/null
@@ -1,279 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "openal_output_plugin.h"
-#include "output_api.h"
-
-#include <glib.h>
-
-#ifndef HAVE_OSX
-#include <AL/al.h>
-#include <AL/alc.h>
-#else
-#include <OpenAL/al.h>
-#include <OpenAL/alc.h>
-#endif
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "openal"
-
-/* should be enough for buffer size = 2048 */
-#define NUM_BUFFERS 16
-
-struct openal_data {
- struct audio_output base;
-
- const char *device_name;
- ALCdevice *device;
- ALCcontext *context;
- ALuint buffers[NUM_BUFFERS];
- unsigned filled;
- ALuint source;
- ALenum format;
- ALuint frequency;
-};
-
-static inline GQuark
-openal_output_quark(void)
-{
- return g_quark_from_static_string("openal_output");
-}
-
-static ALenum
-openal_audio_format(struct audio_format *audio_format)
-{
- /* note: cannot map SAMPLE_FORMAT_S8 to AL_FORMAT_STEREO8 or
- AL_FORMAT_MONO8 since OpenAL expects unsigned 8 bit
- samples, while MPD uses signed samples */
-
- switch (audio_format->format) {
- case SAMPLE_FORMAT_S16:
- if (audio_format->channels == 2)
- return AL_FORMAT_STEREO16;
- if (audio_format->channels == 1)
- return AL_FORMAT_MONO16;
-
- /* fall back to mono */
- audio_format->channels = 1;
- return openal_audio_format(audio_format);
-
- default:
- /* fall back to 16 bit */
- audio_format->format = SAMPLE_FORMAT_S16;
- return openal_audio_format(audio_format);
- }
-}
-
-G_GNUC_PURE
-static inline ALint
-openal_get_source_i(const struct openal_data *od, ALenum param)
-{
- ALint value;
- alGetSourcei(od->source, param, &value);
- return value;
-}
-
-G_GNUC_PURE
-static inline bool
-openal_has_processed(const struct openal_data *od)
-{
- return openal_get_source_i(od, AL_BUFFERS_PROCESSED) > 0;
-}
-
-G_GNUC_PURE
-static inline ALint
-openal_is_playing(const struct openal_data *od)
-{
- return openal_get_source_i(od, AL_SOURCE_STATE) == AL_PLAYING;
-}
-
-static bool
-openal_setup_context(struct openal_data *od,
- GError **error)
-{
- od->device = alcOpenDevice(od->device_name);
-
- if (od->device == NULL) {
- g_set_error(error, openal_output_quark(), 0,
- "Error opening OpenAL device \"%s\"\n",
- od->device_name);
- return false;
- }
-
- od->context = alcCreateContext(od->device, NULL);
-
- if (od->context == NULL) {
- g_set_error(error, openal_output_quark(), 0,
- "Error creating context for \"%s\"\n",
- od->device_name);
- alcCloseDevice(od->device);
- return false;
- }
-
- return true;
-}
-
-static struct audio_output *
-openal_init(const struct config_param *param, GError **error_r)
-{
- const char *device_name = config_get_block_string(param, "device", NULL);
- struct openal_data *od;
-
- if (device_name == NULL) {
- device_name = alcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER);
- }
-
- od = g_new(struct openal_data, 1);
- if (!ao_base_init(&od->base, &openal_output_plugin, param, error_r)) {
- g_free(od);
- return NULL;
- }
-
- od->device_name = device_name;
-
- return &od->base;
-}
-
-static void
-openal_finish(struct audio_output *ao)
-{
- struct openal_data *od = (struct openal_data *)ao;
-
- ao_base_finish(&od->base);
- g_free(od);
-}
-
-static bool
-openal_open(struct audio_output *ao, struct audio_format *audio_format,
- GError **error)
-{
- struct openal_data *od = (struct openal_data *)ao;
-
- od->format = openal_audio_format(audio_format);
-
- if (!openal_setup_context(od, error)) {
- return false;
- }
-
- alcMakeContextCurrent(od->context);
- alGenBuffers(NUM_BUFFERS, od->buffers);
-
- if (alGetError() != AL_NO_ERROR) {
- g_set_error(error, openal_output_quark(), 0,
- "Failed to generate buffers");
- return false;
- }
-
- alGenSources(1, &od->source);
-
- if (alGetError() != AL_NO_ERROR) {
- g_set_error(error, openal_output_quark(), 0,
- "Failed to generate source");
- alDeleteBuffers(NUM_BUFFERS, od->buffers);
- return false;
- }
-
- od->filled = 0;
- od->frequency = audio_format->sample_rate;
-
- return true;
-}
-
-static void
-openal_close(struct audio_output *ao)
-{
- struct openal_data *od = (struct openal_data *)ao;
-
- alcMakeContextCurrent(od->context);
- alDeleteSources(1, &od->source);
- alDeleteBuffers(NUM_BUFFERS, od->buffers);
- alcDestroyContext(od->context);
- alcCloseDevice(od->device);
-}
-
-static unsigned
-openal_delay(struct audio_output *ao)
-{
- struct openal_data *od = (struct openal_data *)ao;
-
- return od->filled < NUM_BUFFERS || openal_has_processed(od)
- ? 0
- /* we don't know exactly how long we must wait for the
- next buffer to finish, so this is a random
- guess: */
- : 50;
-}
-
-static size_t
-openal_play(struct audio_output *ao, const void *chunk, size_t size,
- G_GNUC_UNUSED GError **error)
-{
- struct openal_data *od = (struct openal_data *)ao;
- ALuint buffer;
-
- if (alcGetCurrentContext() != od->context) {
- alcMakeContextCurrent(od->context);
- }
-
- if (od->filled < NUM_BUFFERS) {
- /* fill all buffers */
- buffer = od->buffers[od->filled];
- od->filled++;
- } else {
- /* wait for processed buffer */
- while (!openal_has_processed(od))
- g_usleep(10);
-
- alSourceUnqueueBuffers(od->source, 1, &buffer);
- }
-
- alBufferData(buffer, od->format, chunk, size, od->frequency);
- alSourceQueueBuffers(od->source, 1, &buffer);
-
- if (!openal_is_playing(od))
- alSourcePlay(od->source);
-
- return size;
-}
-
-static void
-openal_cancel(struct audio_output *ao)
-{
- struct openal_data *od = (struct openal_data *)ao;
-
- od->filled = 0;
- alcMakeContextCurrent(od->context);
- alSourceStop(od->source);
-
- /* force-unqueue all buffers */
- alSourcei(od->source, AL_BUFFER, 0);
- od->filled = 0;
-}
-
-const struct audio_output_plugin openal_output_plugin = {
- .name = "openal",
- .init = openal_init,
- .finish = openal_finish,
- .open = openal_open,
- .close = openal_close,
- .delay = openal_delay,
- .play = openal_play,
- .cancel = openal_cancel,
-};
diff --git a/src/output/openal_output_plugin.h b/src/output/openal_output_plugin.h
deleted file mode 100644
index 25f6ccf46..000000000
--- a/src/output/openal_output_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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_OPENAL_OUTPUT_PLUGIN_H
-#define MPD_OPENAL_OUTPUT_PLUGIN_H
-
-extern const struct audio_output_plugin openal_output_plugin;
-
-#endif
diff --git a/src/output/oss_output_plugin.c b/src/output/oss_output_plugin.c
deleted file mode 100644
index e366a4537..000000000
--- a/src/output/oss_output_plugin.c
+++ /dev/null
@@ -1,788 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "oss_output_plugin.h"
-#include "output_api.h"
-#include "mixer_list.h"
-#include "fd_util.h"
-#include "glib_compat.h"
-
-#include <glib.h>
-
-#include <sys/stat.h>
-#include <sys/ioctl.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <assert.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "oss"
-
-#if defined(__OpenBSD__) || defined(__NetBSD__)
-# include <soundcard.h>
-#else /* !(defined(__OpenBSD__) || defined(__NetBSD__) */
-# include <sys/soundcard.h>
-#endif /* !(defined(__OpenBSD__) || defined(__NetBSD__) */
-
-/* We got bug reports from FreeBSD users who said that the two 24 bit
- formats generate white noise on FreeBSD, but 32 bit works. This is
- a workaround until we know what exactly is expected by the kernel
- audio drivers. */
-#ifndef __linux__
-#undef AFMT_S24_PACKED
-#undef AFMT_S24_NE
-#endif
-
-#ifdef AFMT_S24_PACKED
-#include "pcm_export.h"
-#endif
-
-struct oss_data {
- struct audio_output base;
-
-#ifdef AFMT_S24_PACKED
- struct pcm_export_state export;
-#endif
-
- int fd;
- const char *device;
-
- /**
- * The current input audio format. This is needed to reopen
- * the device after cancel().
- */
- struct audio_format audio_format;
-
- /**
- * The current OSS audio format. This is needed to reopen the
- * device after cancel().
- */
- int oss_format;
-};
-
-/**
- * The quark used for GError.domain.
- */
-static inline GQuark
-oss_output_quark(void)
-{
- return g_quark_from_static_string("oss_output");
-}
-
-static struct oss_data *
-oss_data_new(void)
-{
- struct oss_data *ret = g_new(struct oss_data, 1);
-
- ret->device = NULL;
- ret->fd = -1;
-
- return ret;
-}
-
-static void
-oss_data_free(struct oss_data *od)
-{
- g_free(od);
-}
-
-enum oss_stat {
- OSS_STAT_NO_ERROR = 0,
- OSS_STAT_NOT_CHAR_DEV = -1,
- OSS_STAT_NO_PERMS = -2,
- OSS_STAT_DOESN_T_EXIST = -3,
- OSS_STAT_OTHER = -4,
-};
-
-static enum oss_stat
-oss_stat_device(const char *device, int *errno_r)
-{
- struct stat st;
-
- if (0 == stat(device, &st)) {
- if (!S_ISCHR(st.st_mode)) {
- return OSS_STAT_NOT_CHAR_DEV;
- }
- } else {
- *errno_r = errno;
-
- switch (errno) {
- case ENOENT:
- case ENOTDIR:
- return OSS_STAT_DOESN_T_EXIST;
- case EACCES:
- return OSS_STAT_NO_PERMS;
- default:
- return OSS_STAT_OTHER;
- }
- }
-
- return OSS_STAT_NO_ERROR;
-}
-
-static const char *default_devices[] = { "/dev/sound/dsp", "/dev/dsp" };
-
-static bool
-oss_output_test_default_device(void)
-{
- int fd, i;
-
- for (i = G_N_ELEMENTS(default_devices); --i >= 0; ) {
- fd = open_cloexec(default_devices[i], O_WRONLY, 0);
-
- if (fd >= 0) {
- close(fd);
- return true;
- }
- g_warning("Error opening OSS device \"%s\": %s\n",
- default_devices[i], g_strerror(errno));
- }
-
- return false;
-}
-
-static struct audio_output *
-oss_open_default(GError **error)
-{
- int i;
- int err[G_N_ELEMENTS(default_devices)];
- enum oss_stat ret[G_N_ELEMENTS(default_devices)];
-
- for (i = G_N_ELEMENTS(default_devices); --i >= 0; ) {
- ret[i] = oss_stat_device(default_devices[i], &err[i]);
- if (ret[i] == OSS_STAT_NO_ERROR) {
- struct oss_data *od = oss_data_new();
- if (!ao_base_init(&od->base, &oss_output_plugin, NULL,
- error)) {
- g_free(od);
- return NULL;
- }
-
- od->device = default_devices[i];
- return &od->base;
- }
- }
-
- for (i = G_N_ELEMENTS(default_devices); --i >= 0; ) {
- const char *dev = default_devices[i];
- switch(ret[i]) {
- case OSS_STAT_NO_ERROR:
- /* never reached */
- break;
- case OSS_STAT_DOESN_T_EXIST:
- g_warning("%s not found\n", dev);
- break;
- case OSS_STAT_NOT_CHAR_DEV:
- g_warning("%s is not a character device\n", dev);
- break;
- case OSS_STAT_NO_PERMS:
- g_warning("%s: permission denied\n", dev);
- break;
- case OSS_STAT_OTHER:
- g_warning("Error accessing %s: %s\n",
- dev, g_strerror(err[i]));
- }
- }
-
- g_set_error(error, oss_output_quark(), 0,
- "error trying to open default OSS device");
- return NULL;
-}
-
-static struct audio_output *
-oss_output_init(const struct config_param *param, GError **error)
-{
- const char *device = config_get_block_string(param, "device", NULL);
- if (device != NULL) {
- struct oss_data *od = oss_data_new();
- if (!ao_base_init(&od->base, &oss_output_plugin, param,
- error)) {
- g_free(od);
- return NULL;
- }
-
- od->device = device;
- return &od->base;
- }
-
- return oss_open_default(error);
-}
-
-static void
-oss_output_finish(struct audio_output *ao)
-{
- struct oss_data *od = (struct oss_data *)ao;
-
- ao_base_finish(&od->base);
- oss_data_free(od);
-}
-
-#ifdef AFMT_S24_PACKED
-
-static bool
-oss_output_enable(struct audio_output *ao, G_GNUC_UNUSED GError **error_r)
-{
- struct oss_data *od = (struct oss_data *)ao;
-
- pcm_export_init(&od->export);
- return true;
-}
-
-static void
-oss_output_disable(struct audio_output *ao)
-{
- struct oss_data *od = (struct oss_data *)ao;
-
- pcm_export_deinit(&od->export);
-}
-
-#endif
-
-static void
-oss_close(struct oss_data *od)
-{
- if (od->fd >= 0)
- close(od->fd);
- od->fd = -1;
-}
-
-/**
- * A tri-state type for oss_try_ioctl().
- */
-enum oss_setup_result {
- SUCCESS,
- ERROR,
- UNSUPPORTED,
-};
-
-/**
- * Invoke an ioctl on the OSS file descriptor. On success, SUCCESS is
- * returned. If the parameter is not supported, UNSUPPORTED is
- * returned. Any other failure returns ERROR and allocates a GError.
- */
-static enum oss_setup_result
-oss_try_ioctl_r(int fd, unsigned long request, int *value_r,
- const char *msg, GError **error_r)
-{
- assert(fd >= 0);
- assert(value_r != NULL);
- assert(msg != NULL);
- assert(error_r == NULL || *error_r == NULL);
-
- int ret = ioctl(fd, request, value_r);
- if (ret >= 0)
- return SUCCESS;
-
- if (errno == EINVAL)
- return UNSUPPORTED;
-
- g_set_error(error_r, oss_output_quark(), errno,
- "%s: %s", msg, g_strerror(errno));
- return ERROR;
-}
-
-/**
- * Invoke an ioctl on the OSS file descriptor. On success, SUCCESS is
- * returned. If the parameter is not supported, UNSUPPORTED is
- * returned. Any other failure returns ERROR and allocates a GError.
- */
-static enum oss_setup_result
-oss_try_ioctl(int fd, unsigned long request, int value,
- const char *msg, GError **error_r)
-{
- return oss_try_ioctl_r(fd, request, &value, msg, error_r);
-}
-
-/**
- * Set up the channel number, and attempts to find alternatives if the
- * specified number is not supported.
- */
-static bool
-oss_setup_channels(int fd, struct audio_format *audio_format, GError **error_r)
-{
- const char *const msg = "Failed to set channel count";
- int channels = audio_format->channels;
- enum oss_setup_result result =
- oss_try_ioctl_r(fd, SNDCTL_DSP_CHANNELS, &channels, msg, error_r);
- switch (result) {
- case SUCCESS:
- if (!audio_valid_channel_count(channels))
- break;
-
- audio_format->channels = channels;
- return true;
-
- case ERROR:
- return false;
-
- case UNSUPPORTED:
- break;
- }
-
- for (unsigned i = 1; i < 2; ++i) {
- if (i == audio_format->channels)
- /* don't try that again */
- continue;
-
- channels = i;
- result = oss_try_ioctl_r(fd, SNDCTL_DSP_CHANNELS, &channels,
- msg, error_r);
- switch (result) {
- case SUCCESS:
- if (!audio_valid_channel_count(channels))
- break;
-
- audio_format->channels = channels;
- return true;
-
- case ERROR:
- return false;
-
- case UNSUPPORTED:
- break;
- }
- }
-
- g_set_error(error_r, oss_output_quark(), EINVAL, "%s", msg);
- return false;
-}
-
-/**
- * Set up the sample rate, and attempts to find alternatives if the
- * specified sample rate is not supported.
- */
-static bool
-oss_setup_sample_rate(int fd, struct audio_format *audio_format,
- GError **error_r)
-{
- const char *const msg = "Failed to set sample rate";
- int sample_rate = audio_format->sample_rate;
- enum oss_setup_result result =
- oss_try_ioctl_r(fd, SNDCTL_DSP_SPEED, &sample_rate,
- msg, error_r);
- switch (result) {
- case SUCCESS:
- if (!audio_valid_sample_rate(sample_rate))
- break;
-
- audio_format->sample_rate = sample_rate;
- return true;
-
- case ERROR:
- return false;
-
- case UNSUPPORTED:
- break;
- }
-
- static const int sample_rates[] = { 48000, 44100, 0 };
- for (unsigned i = 0; sample_rates[i] != 0; ++i) {
- sample_rate = sample_rates[i];
- if (sample_rate == (int)audio_format->sample_rate)
- continue;
-
- result = oss_try_ioctl_r(fd, SNDCTL_DSP_SPEED, &sample_rate,
- msg, error_r);
- switch (result) {
- case SUCCESS:
- if (!audio_valid_sample_rate(sample_rate))
- break;
-
- audio_format->sample_rate = sample_rate;
- return true;
-
- case ERROR:
- return false;
-
- case UNSUPPORTED:
- break;
- }
- }
-
- g_set_error(error_r, oss_output_quark(), EINVAL, "%s", msg);
- return false;
-}
-
-/**
- * Convert a MPD sample format to its OSS counterpart. Returns
- * AFMT_QUERY if there is no direct counterpart.
- */
-static int
-sample_format_to_oss(enum sample_format format)
-{
- switch (format) {
- case SAMPLE_FORMAT_UNDEFINED:
- case SAMPLE_FORMAT_FLOAT:
- case SAMPLE_FORMAT_DSD:
- return AFMT_QUERY;
-
- case SAMPLE_FORMAT_S8:
- return AFMT_S8;
-
- case SAMPLE_FORMAT_S16:
- return AFMT_S16_NE;
-
- case SAMPLE_FORMAT_S24_P32:
-#ifdef AFMT_S24_NE
- return AFMT_S24_NE;
-#else
- return AFMT_QUERY;
-#endif
-
- case SAMPLE_FORMAT_S32:
-#ifdef AFMT_S32_NE
- return AFMT_S32_NE;
-#else
- return AFMT_QUERY;
-#endif
- }
-
- return AFMT_QUERY;
-}
-
-/**
- * Convert an OSS sample format to its MPD counterpart. Returns
- * SAMPLE_FORMAT_UNDEFINED if there is no direct counterpart.
- */
-static enum sample_format
-sample_format_from_oss(int format)
-{
- switch (format) {
- case AFMT_S8:
- return SAMPLE_FORMAT_S8;
-
- case AFMT_S16_NE:
- return SAMPLE_FORMAT_S16;
-
-#ifdef AFMT_S24_PACKED
- case AFMT_S24_PACKED:
- return SAMPLE_FORMAT_S24_P32;
-#endif
-
-#ifdef AFMT_S24_NE
- case AFMT_S24_NE:
- return SAMPLE_FORMAT_S24_P32;
-#endif
-
-#ifdef AFMT_S32_NE
- case AFMT_S32_NE:
- return SAMPLE_FORMAT_S32;
-#endif
-
- default:
- return SAMPLE_FORMAT_UNDEFINED;
- }
-}
-
-/**
- * Probe one sample format.
- *
- * @return the selected sample format or SAMPLE_FORMAT_UNDEFINED on
- * error
- */
-static enum oss_setup_result
-oss_probe_sample_format(int fd, enum sample_format sample_format,
- enum sample_format *sample_format_r,
- int *oss_format_r,
-#ifdef AFMT_S24_PACKED
- struct pcm_export_state *export,
-#endif
- GError **error_r)
-{
- int oss_format = sample_format_to_oss(sample_format);
- if (oss_format == AFMT_QUERY)
- return UNSUPPORTED;
-
- enum oss_setup_result result =
- oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE,
- &oss_format,
- "Failed to set sample format", error_r);
-
-#ifdef AFMT_S24_PACKED
- if (result == UNSUPPORTED && sample_format == SAMPLE_FORMAT_S24_P32) {
- /* if the driver doesn't support padded 24 bit, try
- packed 24 bit */
- oss_format = AFMT_S24_PACKED;
- result = oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE,
- &oss_format,
- "Failed to set sample format", error_r);
- }
-#endif
-
- if (result != SUCCESS)
- return result;
-
- sample_format = sample_format_from_oss(oss_format);
- if (sample_format == SAMPLE_FORMAT_UNDEFINED)
- return UNSUPPORTED;
-
- *sample_format_r = sample_format;
- *oss_format_r = oss_format;
-
-#ifdef AFMT_S24_PACKED
- pcm_export_open(export, sample_format, 0, false, false,
- oss_format == AFMT_S24_PACKED,
- oss_format == AFMT_S24_PACKED &&
- G_BYTE_ORDER != G_LITTLE_ENDIAN);
-#endif
-
- return SUCCESS;
-}
-
-/**
- * Set up the sample format, and attempts to find alternatives if the
- * specified format is not supported.
- */
-static bool
-oss_setup_sample_format(int fd, struct audio_format *audio_format,
- int *oss_format_r,
-#ifdef AFMT_S24_PACKED
- struct pcm_export_state *export,
-#endif
- GError **error_r)
-{
- enum sample_format mpd_format;
- enum oss_setup_result result =
- oss_probe_sample_format(fd, audio_format->format,
- &mpd_format, oss_format_r,
-#ifdef AFMT_S24_PACKED
- export,
-#endif
- error_r);
- switch (result) {
- case SUCCESS:
- audio_format->format = mpd_format;
- return true;
-
- case ERROR:
- return false;
-
- case UNSUPPORTED:
- break;
- }
-
- if (result != UNSUPPORTED)
- return result == SUCCESS;
-
- /* the requested sample format is not available - probe for
- other formats supported by MPD */
-
- static const enum sample_format sample_formats[] = {
- SAMPLE_FORMAT_S24_P32,
- SAMPLE_FORMAT_S32,
- SAMPLE_FORMAT_S16,
- SAMPLE_FORMAT_S8,
- SAMPLE_FORMAT_UNDEFINED /* sentinel */
- };
-
- for (unsigned i = 0; sample_formats[i] != SAMPLE_FORMAT_UNDEFINED; ++i) {
- mpd_format = sample_formats[i];
- if (mpd_format == audio_format->format)
- /* don't try that again */
- continue;
-
- result = oss_probe_sample_format(fd, mpd_format,
- &mpd_format, oss_format_r,
-#ifdef AFMT_S24_PACKED
- export,
-#endif
- error_r);
- switch (result) {
- case SUCCESS:
- audio_format->format = mpd_format;
- return true;
-
- case ERROR:
- return false;
-
- case UNSUPPORTED:
- break;
- }
- }
-
- g_set_error_literal(error_r, oss_output_quark(), EINVAL,
- "Failed to set sample format");
- return false;
-}
-
-/**
- * Sets up the OSS device which was opened before.
- */
-static bool
-oss_setup(struct oss_data *od, struct audio_format *audio_format,
- GError **error_r)
-{
- return oss_setup_channels(od->fd, audio_format, error_r) &&
- oss_setup_sample_rate(od->fd, audio_format, error_r) &&
- oss_setup_sample_format(od->fd, audio_format, &od->oss_format,
-#ifdef AFMT_S24_PACKED
- &od->export,
-#endif
- error_r);
-}
-
-/**
- * Reopen the device with the saved audio_format, without any probing.
- */
-static bool
-oss_reopen(struct oss_data *od, GError **error_r)
-{
- assert(od->fd < 0);
-
- od->fd = open_cloexec(od->device, O_WRONLY, 0);
- if (od->fd < 0) {
- g_set_error(error_r, oss_output_quark(), errno,
- "Error opening OSS device \"%s\": %s",
- od->device, g_strerror(errno));
- return false;
- }
-
- enum oss_setup_result result;
-
- const char *const msg1 = "Failed to set channel count";
- result = oss_try_ioctl(od->fd, SNDCTL_DSP_CHANNELS,
- od->audio_format.channels, msg1, error_r);
- if (result != SUCCESS) {
- oss_close(od);
- if (result == UNSUPPORTED)
- g_set_error(error_r, oss_output_quark(), EINVAL,
- "%s", msg1);
- return false;
- }
-
- const char *const msg2 = "Failed to set sample rate";
- result = oss_try_ioctl(od->fd, SNDCTL_DSP_SPEED,
- od->audio_format.sample_rate, msg2, error_r);
- if (result != SUCCESS) {
- oss_close(od);
- if (result == UNSUPPORTED)
- g_set_error(error_r, oss_output_quark(), EINVAL,
- "%s", msg2);
- return false;
- }
-
- const char *const msg3 = "Failed to set sample format";
- result = oss_try_ioctl(od->fd, SNDCTL_DSP_SAMPLESIZE,
- od->oss_format,
- msg3, error_r);
- if (result != SUCCESS) {
- oss_close(od);
- if (result == UNSUPPORTED)
- g_set_error(error_r, oss_output_quark(), EINVAL,
- "%s", msg3);
- return false;
- }
-
- return true;
-}
-
-static bool
-oss_output_open(struct audio_output *ao, struct audio_format *audio_format,
- GError **error)
-{
- struct oss_data *od = (struct oss_data *)ao;
-
- od->fd = open_cloexec(od->device, O_WRONLY, 0);
- if (od->fd < 0) {
- g_set_error(error, oss_output_quark(), errno,
- "Error opening OSS device \"%s\": %s",
- od->device, g_strerror(errno));
- return false;
- }
-
- if (!oss_setup(od, audio_format, error)) {
- oss_close(od);
- return false;
- }
-
- od->audio_format = *audio_format;
- return true;
-}
-
-static void
-oss_output_close(struct audio_output *ao)
-{
- struct oss_data *od = (struct oss_data *)ao;
-
- oss_close(od);
-}
-
-static void
-oss_output_cancel(struct audio_output *ao)
-{
- struct oss_data *od = (struct oss_data *)ao;
-
- if (od->fd >= 0) {
- ioctl(od->fd, SNDCTL_DSP_RESET, 0);
- oss_close(od);
- }
-}
-
-static size_t
-oss_output_play(struct audio_output *ao, const void *chunk, size_t size,
- GError **error)
-{
- struct oss_data *od = (struct oss_data *)ao;
- ssize_t ret;
-
- /* reopen the device since it was closed by dropBufferedAudio */
- if (od->fd < 0 && !oss_reopen(od, error))
- return 0;
-
-#ifdef AFMT_S24_PACKED
- chunk = pcm_export(&od->export, chunk, size, &size);
-#endif
-
- while (true) {
- ret = write(od->fd, chunk, size);
- if (ret > 0) {
-#ifdef AFMT_S24_PACKED
- ret = pcm_export_source_size(&od->export, ret);
-#endif
- return ret;
- }
-
- if (ret < 0 && errno != EINTR) {
- g_set_error(error, oss_output_quark(), errno,
- "Write error on %s: %s",
- od->device, g_strerror(errno));
- return 0;
- }
- }
-}
-
-const struct audio_output_plugin oss_output_plugin = {
- .name = "oss",
- .test_default_device = oss_output_test_default_device,
- .init = oss_output_init,
- .finish = oss_output_finish,
-#ifdef AFMT_S24_PACKED
- .enable = oss_output_enable,
- .disable = oss_output_disable,
-#endif
- .open = oss_output_open,
- .close = oss_output_close,
- .play = oss_output_play,
- .cancel = oss_output_cancel,
-
- .mixer_plugin = &oss_mixer_plugin,
-};
diff --git a/src/output/oss_output_plugin.h b/src/output/oss_output_plugin.h
deleted file mode 100644
index 2aecc2b3a..000000000
--- a/src/output/oss_output_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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_OSS_OUTPUT_PLUGIN_H
-#define MPD_OSS_OUTPUT_PLUGIN_H
-
-extern const struct audio_output_plugin oss_output_plugin;
-
-#endif
diff --git a/src/output/osx_output_plugin.c b/src/output/osx_output_plugin.c
deleted file mode 100644
index cd51fca20..000000000
--- a/src/output/osx_output_plugin.c
+++ /dev/null
@@ -1,438 +0,0 @@
-/*
- * 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 "osx_output_plugin.h"
-#include "output_api.h"
-#include "fifo_buffer.h"
-
-#include <glib.h>
-#include <CoreAudio/AudioHardware.h>
-#include <AudioUnit/AudioUnit.h>
-#include <CoreServices/CoreServices.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "osx"
-
-struct osx_output {
- struct audio_output base;
-
- /* configuration settings */
- OSType component_subtype;
- /* only applicable with kAudioUnitSubType_HALOutput */
- const char *device_name;
-
- AudioUnit au;
- GMutex *mutex;
- GCond *condition;
-
- struct fifo_buffer *buffer;
-};
-
-/**
- * The quark used for GError.domain.
- */
-static inline GQuark
-osx_output_quark(void)
-{
- return g_quark_from_static_string("osx_output");
-}
-
-static bool
-osx_output_test_default_device(void)
-{
- /* on a Mac, this is always the default plugin, if nothing
- else is configured */
- return true;
-}
-
-static void
-osx_output_configure(struct osx_output *oo, const struct config_param *param)
-{
- const char *device = config_get_block_string(param, "device", NULL);
-
- if (device == NULL || 0 == strcmp(device, "default")) {
- oo->component_subtype = kAudioUnitSubType_DefaultOutput;
- oo->device_name = NULL;
- }
- else if (0 == strcmp(device, "system")) {
- oo->component_subtype = kAudioUnitSubType_SystemOutput;
- oo->device_name = NULL;
- }
- else {
- oo->component_subtype = kAudioUnitSubType_HALOutput;
- /* XXX am I supposed to g_strdup() this? */
- oo->device_name = device;
- }
-}
-
-static struct audio_output *
-osx_output_init(const struct config_param *param, GError **error_r)
-{
- struct osx_output *oo = g_new(struct osx_output, 1);
- if (!ao_base_init(&oo->base, &osx_output_plugin, param, error_r)) {
- g_free(oo);
- return NULL;
- }
-
- osx_output_configure(oo, param);
- oo->mutex = g_mutex_new();
- oo->condition = g_cond_new();
-
- return &oo->base;
-}
-
-static void
-osx_output_finish(struct audio_output *ao)
-{
- struct osx_output *od = (struct osx_output *)ao;
-
- g_mutex_free(od->mutex);
- g_cond_free(od->condition);
- g_free(od);
-}
-
-static bool
-osx_output_set_device(struct osx_output *oo, GError **error)
-{
- bool ret = true;
- OSStatus status;
- UInt32 size, numdevices;
- AudioDeviceID *deviceids = NULL;
- char name[256];
- unsigned int i;
-
- if (oo->component_subtype != kAudioUnitSubType_HALOutput)
- goto done;
-
- /* how many audio devices are there? */
- status = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices,
- &size,
- NULL);
- if (status != noErr) {
- g_set_error(error, osx_output_quark(), status,
- "Unable to determine number of OS X audio devices: %s",
- GetMacOSStatusCommentString(status));
- ret = false;
- goto done;
- }
-
- /* what are the available audio device IDs? */
- numdevices = size / sizeof(AudioDeviceID);
- deviceids = g_malloc(size);
- status = AudioHardwareGetProperty(kAudioHardwarePropertyDevices,
- &size,
- deviceids);
- if (status != noErr) {
- g_set_error(error, osx_output_quark(), status,
- "Unable to determine OS X audio device IDs: %s",
- GetMacOSStatusCommentString(status));
- ret = false;
- goto done;
- }
-
- /* which audio device matches oo->device_name? */
- for (i = 0; i < numdevices; i++) {
- size = sizeof(name);
- status = AudioDeviceGetProperty(deviceids[i], 0, false,
- kAudioDevicePropertyDeviceName,
- &size, name);
- if (status != noErr) {
- g_set_error(error, osx_output_quark(), status,
- "Unable to determine OS X device name "
- "(device %u): %s",
- (unsigned int) deviceids[i],
- GetMacOSStatusCommentString(status));
- ret = false;
- goto done;
- }
- if (strcmp(oo->device_name, name) == 0) {
- g_debug("found matching device: ID=%u, name=%s",
- (unsigned int) deviceids[i], name);
- break;
- }
- }
- if (i == numdevices) {
- g_warning("Found no audio device with name '%s' "
- "(will use default audio device)",
- oo->device_name);
- goto done;
- }
-
- status = AudioUnitSetProperty(oo->au,
- kAudioOutputUnitProperty_CurrentDevice,
- kAudioUnitScope_Global,
- 0,
- &(deviceids[i]),
- sizeof(AudioDeviceID));
- if (status != noErr) {
- g_set_error(error, osx_output_quark(), status,
- "Unable to set OS X audio output device: %s",
- GetMacOSStatusCommentString(status));
- ret = false;
- goto done;
- }
- g_debug("set OS X audio output device ID=%u, name=%s",
- (unsigned int) deviceids[i], name);
-
-done:
- if (deviceids != NULL)
- g_free(deviceids);
- return ret;
-}
-
-static OSStatus
-osx_render(void *vdata,
- G_GNUC_UNUSED AudioUnitRenderActionFlags *io_action_flags,
- G_GNUC_UNUSED const AudioTimeStamp *in_timestamp,
- G_GNUC_UNUSED UInt32 in_bus_number,
- G_GNUC_UNUSED UInt32 in_number_frames,
- AudioBufferList *buffer_list)
-{
- struct osx_output *od = (struct osx_output *) vdata;
- AudioBuffer *buffer = &buffer_list->mBuffers[0];
- size_t buffer_size = buffer->mDataByteSize;
-
- assert(od->buffer != NULL);
-
- g_mutex_lock(od->mutex);
-
- size_t nbytes;
- const void *src = fifo_buffer_read(od->buffer, &nbytes);
-
- if (src != NULL) {
- if (nbytes > buffer_size)
- nbytes = buffer_size;
-
- memcpy(buffer->mData, src, nbytes);
- fifo_buffer_consume(od->buffer, nbytes);
- } else
- nbytes = 0;
-
- g_cond_signal(od->condition);
- g_mutex_unlock(od->mutex);
-
- buffer->mDataByteSize = nbytes;
-
- unsigned i;
- for (i = 1; i < buffer_list->mNumberBuffers; ++i) {
- buffer = &buffer_list->mBuffers[i];
- buffer->mDataByteSize = 0;
- }
-
- return 0;
-}
-
-static bool
-osx_output_enable(struct audio_output *ao, GError **error_r)
-{
- struct osx_output *oo = (struct osx_output *)ao;
-
- ComponentDescription desc;
- desc.componentType = kAudioUnitType_Output;
- desc.componentSubType = oo->component_subtype;
- desc.componentManufacturer = kAudioUnitManufacturer_Apple;
- desc.componentFlags = 0;
- desc.componentFlagsMask = 0;
-
- Component comp = FindNextComponent(NULL, &desc);
- if (comp == 0) {
- g_set_error(error_r, osx_output_quark(), 0,
- "Error finding OS X component");
- return false;
- }
-
- OSStatus status = OpenAComponent(comp, &oo->au);
- if (status != noErr) {
- g_set_error(error_r, osx_output_quark(), status,
- "Unable to open OS X component: %s",
- GetMacOSStatusCommentString(status));
- return false;
- }
-
- if (!osx_output_set_device(oo, error_r)) {
- CloseComponent(oo->au);
- return false;
- }
-
- AURenderCallbackStruct callback;
- callback.inputProc = osx_render;
- callback.inputProcRefCon = oo;
-
- ComponentResult result =
- AudioUnitSetProperty(oo->au,
- kAudioUnitProperty_SetRenderCallback,
- kAudioUnitScope_Input, 0,
- &callback, sizeof(callback));
- if (result != noErr) {
- CloseComponent(oo->au);
- g_set_error(error_r, osx_output_quark(), result,
- "unable to set callback for OS X audio unit");
- return false;
- }
-
- return true;
-}
-
-static void
-osx_output_disable(struct audio_output *ao)
-{
- struct osx_output *oo = (struct osx_output *)ao;
-
- CloseComponent(oo->au);
-}
-
-static void
-osx_output_cancel(struct audio_output *ao)
-{
- struct osx_output *od = (struct osx_output *)ao;
-
- g_mutex_lock(od->mutex);
- fifo_buffer_clear(od->buffer);
- g_mutex_unlock(od->mutex);
-}
-
-static void
-osx_output_close(struct audio_output *ao)
-{
- struct osx_output *od = (struct osx_output *)ao;
-
- AudioOutputUnitStop(od->au);
- AudioUnitUninitialize(od->au);
-
- fifo_buffer_free(od->buffer);
-}
-
-static bool
-osx_output_open(struct audio_output *ao, struct audio_format *audio_format, GError **error)
-{
- struct osx_output *od = (struct osx_output *)ao;
-
- AudioStreamBasicDescription stream_description;
- stream_description.mSampleRate = audio_format->sample_rate;
- stream_description.mFormatID = kAudioFormatLinearPCM;
- stream_description.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
-
- switch (audio_format->format) {
- case SAMPLE_FORMAT_S8:
- stream_description.mBitsPerChannel = 8;
- break;
-
- case SAMPLE_FORMAT_S16:
- stream_description.mBitsPerChannel = 16;
- break;
-
- case SAMPLE_FORMAT_S32:
- stream_description.mBitsPerChannel = 32;
- break;
-
- default:
- audio_format->format = SAMPLE_FORMAT_S32;
- stream_description.mBitsPerChannel = 32;
- break;
- }
-
-#if G_BYTE_ORDER == G_BIG_ENDIAN
- stream_description.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
-#endif
-
- stream_description.mBytesPerPacket =
- audio_format_frame_size(audio_format);
- stream_description.mFramesPerPacket = 1;
- stream_description.mBytesPerFrame = stream_description.mBytesPerPacket;
- stream_description.mChannelsPerFrame = audio_format->channels;
-
- ComponentResult result =
- AudioUnitSetProperty(od->au, kAudioUnitProperty_StreamFormat,
- kAudioUnitScope_Input, 0,
- &stream_description,
- sizeof(stream_description));
- if (result != noErr) {
- g_set_error(error, osx_output_quark(), result,
- "Unable to set format on OS X device");
- return false;
- }
-
- OSStatus status = AudioUnitInitialize(od->au);
- if (status != noErr) {
- g_set_error(error, osx_output_quark(), status,
- "Unable to initialize OS X audio unit: %s",
- GetMacOSStatusCommentString(status));
- return false;
- }
-
- /* create a buffer of 1s */
- od->buffer = fifo_buffer_new(audio_format->sample_rate *
- audio_format_frame_size(audio_format));
-
- status = AudioOutputUnitStart(od->au);
- if (status != 0) {
- AudioUnitUninitialize(od->au);
- g_set_error(error, osx_output_quark(), status,
- "unable to start audio output: %s",
- GetMacOSStatusCommentString(status));
- return false;
- }
-
- return true;
-}
-
-static size_t
-osx_output_play(struct audio_output *ao, const void *chunk, size_t size,
- G_GNUC_UNUSED GError **error)
-{
- struct osx_output *od = (struct osx_output *)ao;
-
- g_mutex_lock(od->mutex);
-
- void *dest;
- size_t max_length;
-
- while (true) {
- dest = fifo_buffer_write(od->buffer, &max_length);
- if (dest != NULL)
- break;
-
- /* wait for some free space in the buffer */
- g_cond_wait(od->condition, od->mutex);
- }
-
- if (size > max_length)
- size = max_length;
-
- memcpy(dest, chunk, size);
- fifo_buffer_append(od->buffer, size);
-
- g_mutex_unlock(od->mutex);
-
- return size;
-}
-
-const struct audio_output_plugin osx_output_plugin = {
- .name = "osx",
- .test_default_device = osx_output_test_default_device,
- .init = osx_output_init,
- .finish = osx_output_finish,
- .enable = osx_output_enable,
- .disable = osx_output_disable,
- .open = osx_output_open,
- .close = osx_output_close,
- .play = osx_output_play,
- .cancel = osx_output_cancel,
-};
diff --git a/src/output/osx_output_plugin.h b/src/output/osx_output_plugin.h
deleted file mode 100644
index 814702d4f..000000000
--- a/src/output/osx_output_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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_OSX_OUTPUT_PLUGIN_H
-#define MPD_OSX_OUTPUT_PLUGIN_H
-
-extern const struct audio_output_plugin osx_output_plugin;
-
-#endif
diff --git a/src/output/pipe_output_plugin.c b/src/output/pipe_output_plugin.c
deleted file mode 100644
index 90c5a5331..000000000
--- a/src/output/pipe_output_plugin.c
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "pipe_output_plugin.h"
-#include "output_api.h"
-
-#include <stdio.h>
-#include <errno.h>
-
-struct pipe_output {
- struct audio_output base;
-
- char *cmd;
- FILE *fh;
-};
-
-/**
- * The quark used for GError.domain.
- */
-static inline GQuark
-pipe_output_quark(void)
-{
- return g_quark_from_static_string("pipe_output");
-}
-
-static struct audio_output *
-pipe_output_init(const struct config_param *param,
- GError **error)
-{
- struct pipe_output *pd = g_new(struct pipe_output, 1);
-
- if (!ao_base_init(&pd->base, &pipe_output_plugin, param, error)) {
- g_free(pd);
- return NULL;
- }
-
- pd->cmd = config_dup_block_string(param, "command", NULL);
- if (pd->cmd == NULL) {
- g_set_error(error, pipe_output_quark(), 0,
- "No \"command\" parameter specified");
- return NULL;
- }
-
- return &pd->base;
-}
-
-static void
-pipe_output_finish(struct audio_output *ao)
-{
- struct pipe_output *pd = (struct pipe_output *)ao;
-
- g_free(pd->cmd);
- ao_base_finish(&pd->base);
- g_free(pd);
-}
-
-static bool
-pipe_output_open(struct audio_output *ao,
- G_GNUC_UNUSED struct audio_format *audio_format,
- G_GNUC_UNUSED GError **error)
-{
- struct pipe_output *pd = (struct pipe_output *)ao;
-
- pd->fh = popen(pd->cmd, "w");
- if (pd->fh == NULL) {
- g_set_error(error, pipe_output_quark(), errno,
- "Error opening pipe \"%s\": %s",
- pd->cmd, g_strerror(errno));
- return false;
- }
-
- return true;
-}
-
-static void
-pipe_output_close(struct audio_output *ao)
-{
- struct pipe_output *pd = (struct pipe_output *)ao;
-
- pclose(pd->fh);
-}
-
-static size_t
-pipe_output_play(struct audio_output *ao, const void *chunk, size_t size, GError **error)
-{
- struct pipe_output *pd = (struct pipe_output *)ao;
- size_t ret;
-
- ret = fwrite(chunk, 1, size, pd->fh);
- if (ret == 0)
- g_set_error(error, pipe_output_quark(), errno,
- "Write error on pipe: %s", g_strerror(errno));
-
- return ret;
-}
-
-const struct audio_output_plugin pipe_output_plugin = {
- .name = "pipe",
- .init = pipe_output_init,
- .finish = pipe_output_finish,
- .open = pipe_output_open,
- .close = pipe_output_close,
- .play = pipe_output_play,
-};
diff --git a/src/output/pipe_output_plugin.h b/src/output/pipe_output_plugin.h
deleted file mode 100644
index 9f014f829..000000000
--- a/src/output/pipe_output_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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_PIPE_OUTPUT_PLUGIN_H
-#define MPD_PIPE_OUTPUT_PLUGIN_H
-
-extern const struct audio_output_plugin pipe_output_plugin;
-
-#endif
diff --git a/src/output/pulse_output_plugin.c b/src/output/pulse_output_plugin.c
deleted file mode 100644
index e267427df..000000000
--- a/src/output/pulse_output_plugin.c
+++ /dev/null
@@ -1,955 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "pulse_output_plugin.h"
-#include "output_api.h"
-#include "mixer_list.h"
-#include "mixer/pulse_mixer_plugin.h"
-
-#include <glib.h>
-
-#include <pulse/thread-mainloop.h>
-#include <pulse/context.h>
-#include <pulse/stream.h>
-#include <pulse/introspect.h>
-#include <pulse/subscribe.h>
-#include <pulse/error.h>
-#include <pulse/version.h>
-
-#include <assert.h>
-#include <stddef.h>
-
-#define MPD_PULSE_NAME "Music Player Daemon"
-
-#if !defined(PA_CHECK_VERSION)
-/**
- * This macro was implemented in libpulse 0.9.16.
- */
-#define PA_CHECK_VERSION(a,b,c) false
-#endif
-
-struct pulse_output {
- struct audio_output base;
-
- const char *name;
- const char *server;
- const char *sink;
-
- struct pulse_mixer *mixer;
-
- struct pa_threaded_mainloop *mainloop;
- struct pa_context *context;
- struct pa_stream *stream;
-
- size_t writable;
-
-#if !PA_CHECK_VERSION(0,9,11)
- /**
- * We need this variable because pa_stream_is_corked() wasn't
- * added before 0.9.11.
- */
- bool pause;
-#endif
-};
-
-/**
- * The quark used for GError.domain.
- */
-static inline GQuark
-pulse_output_quark(void)
-{
- return g_quark_from_static_string("pulse_output");
-}
-
-void
-pulse_output_lock(struct pulse_output *po)
-{
- pa_threaded_mainloop_lock(po->mainloop);
-}
-
-void
-pulse_output_unlock(struct pulse_output *po)
-{
- pa_threaded_mainloop_unlock(po->mainloop);
-}
-
-void
-pulse_output_set_mixer(struct pulse_output *po, struct pulse_mixer *pm)
-{
- assert(po != NULL);
- assert(po->mixer == NULL);
- assert(pm != NULL);
-
- po->mixer = pm;
-
- if (po->mainloop == NULL)
- return;
-
- pa_threaded_mainloop_lock(po->mainloop);
-
- if (po->context != NULL &&
- pa_context_get_state(po->context) == PA_CONTEXT_READY) {
- pulse_mixer_on_connect(pm, po->context);
-
- if (po->stream != NULL &&
- pa_stream_get_state(po->stream) == PA_STREAM_READY)
- pulse_mixer_on_change(pm, po->context, po->stream);
- }
-
- pa_threaded_mainloop_unlock(po->mainloop);
-}
-
-void
-pulse_output_clear_mixer(struct pulse_output *po,
- G_GNUC_UNUSED struct pulse_mixer *pm)
-{
- assert(po != NULL);
- assert(pm != NULL);
- assert(po->mixer == pm);
-
- po->mixer = NULL;
-}
-
-bool
-pulse_output_set_volume(struct pulse_output *po,
- const struct pa_cvolume *volume, GError **error_r)
-{
- pa_operation *o;
-
- if (po->context == NULL || po->stream == NULL ||
- pa_stream_get_state(po->stream) != PA_STREAM_READY) {
- g_set_error(error_r, pulse_output_quark(), 0, "disconnected");
- return false;
- }
-
- o = pa_context_set_sink_input_volume(po->context,
- pa_stream_get_index(po->stream),
- volume, NULL, NULL);
- if (o == NULL) {
- g_set_error(error_r, pulse_output_quark(), 0,
- "failed to set PulseAudio volume: %s",
- pa_strerror(pa_context_errno(po->context)));
- return false;
- }
-
- pa_operation_unref(o);
- return true;
-}
-
-/**
- * \brief waits for a pulseaudio operation to finish, frees it and
- * unlocks the mainloop
- * \param operation the operation to wait for
- * \return true if operation has finished normally (DONE state),
- * false otherwise
- */
-static bool
-pulse_wait_for_operation(struct pa_threaded_mainloop *mainloop,
- struct pa_operation *operation)
-{
- pa_operation_state_t state;
-
- assert(mainloop != NULL);
- assert(operation != NULL);
-
- state = pa_operation_get_state(operation);
- while (state == PA_OPERATION_RUNNING) {
- pa_threaded_mainloop_wait(mainloop);
- state = pa_operation_get_state(operation);
- }
-
- pa_operation_unref(operation);
-
- return state == PA_OPERATION_DONE;
-}
-
-/**
- * Callback function for stream operation. It just sends a signal to
- * the caller thread, to wake pulse_wait_for_operation() up.
- */
-static void
-pulse_output_stream_success_cb(G_GNUC_UNUSED pa_stream *s,
- G_GNUC_UNUSED int success, void *userdata)
-{
- struct pulse_output *po = userdata;
-
- pa_threaded_mainloop_signal(po->mainloop, 0);
-}
-
-static void
-pulse_output_context_state_cb(struct pa_context *context, void *userdata)
-{
- struct pulse_output *po = userdata;
-
- switch (pa_context_get_state(context)) {
- case PA_CONTEXT_READY:
- if (po->mixer != NULL)
- pulse_mixer_on_connect(po->mixer, context);
-
- pa_threaded_mainloop_signal(po->mainloop, 0);
- break;
-
- case PA_CONTEXT_TERMINATED:
- case PA_CONTEXT_FAILED:
- if (po->mixer != NULL)
- pulse_mixer_on_disconnect(po->mixer);
-
- /* the caller thread might be waiting for these
- states */
- pa_threaded_mainloop_signal(po->mainloop, 0);
- break;
-
- case PA_CONTEXT_UNCONNECTED:
- case PA_CONTEXT_CONNECTING:
- case PA_CONTEXT_AUTHORIZING:
- case PA_CONTEXT_SETTING_NAME:
- break;
- }
-}
-
-static void
-pulse_output_subscribe_cb(pa_context *context,
- pa_subscription_event_type_t t,
- uint32_t idx, void *userdata)
-{
- struct pulse_output *po = userdata;
- pa_subscription_event_type_t facility
- = t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK;
- pa_subscription_event_type_t type
- = t & PA_SUBSCRIPTION_EVENT_TYPE_MASK;
-
- if (po->mixer != NULL &&
- facility == PA_SUBSCRIPTION_EVENT_SINK_INPUT &&
- po->stream != NULL &&
- pa_stream_get_state(po->stream) == PA_STREAM_READY &&
- idx == pa_stream_get_index(po->stream) &&
- (type == PA_SUBSCRIPTION_EVENT_NEW ||
- type == PA_SUBSCRIPTION_EVENT_CHANGE))
- pulse_mixer_on_change(po->mixer, context, po->stream);
-}
-
-/**
- * Attempt to connect asynchronously to the PulseAudio server.
- *
- * @return true on success, false on error
- */
-static bool
-pulse_output_connect(struct pulse_output *po, GError **error_r)
-{
- assert(po != NULL);
- assert(po->context != NULL);
-
- int error;
-
- error = pa_context_connect(po->context, po->server,
- (pa_context_flags_t)0, NULL);
- if (error < 0) {
- g_set_error(error_r, pulse_output_quark(), 0,
- "pa_context_connect() has failed: %s",
- pa_strerror(pa_context_errno(po->context)));
- return false;
- }
-
- return true;
-}
-
-/**
- * Frees and clears the stream.
- */
-static void
-pulse_output_delete_stream(struct pulse_output *po)
-{
- assert(po != NULL);
- assert(po->stream != NULL);
-
-#if PA_CHECK_VERSION(0,9,8)
- pa_stream_set_suspended_callback(po->stream, NULL, NULL);
-#endif
-
- pa_stream_set_state_callback(po->stream, NULL, NULL);
- pa_stream_set_write_callback(po->stream, NULL, NULL);
-
- pa_stream_disconnect(po->stream);
- pa_stream_unref(po->stream);
- po->stream = NULL;
-}
-
-/**
- * Frees and clears the context.
- *
- * Caller must lock the main loop.
- */
-static void
-pulse_output_delete_context(struct pulse_output *po)
-{
- assert(po != NULL);
- assert(po->context != NULL);
-
- pa_context_set_state_callback(po->context, NULL, NULL);
- pa_context_set_subscribe_callback(po->context, NULL, NULL);
-
- pa_context_disconnect(po->context);
- pa_context_unref(po->context);
- po->context = NULL;
-}
-
-/**
- * Create, set up and connect a context.
- *
- * Caller must lock the main loop.
- *
- * @return true on success, false on error
- */
-static bool
-pulse_output_setup_context(struct pulse_output *po, GError **error_r)
-{
- assert(po != NULL);
- assert(po->mainloop != NULL);
-
- po->context = pa_context_new(pa_threaded_mainloop_get_api(po->mainloop),
- MPD_PULSE_NAME);
- if (po->context == NULL) {
- g_set_error(error_r, pulse_output_quark(), 0,
- "pa_context_new() has failed");
- return false;
- }
-
- pa_context_set_state_callback(po->context,
- pulse_output_context_state_cb, po);
- pa_context_set_subscribe_callback(po->context,
- pulse_output_subscribe_cb, po);
-
- if (!pulse_output_connect(po, error_r)) {
- pulse_output_delete_context(po);
- return false;
- }
-
- return true;
-}
-
-static struct audio_output *
-pulse_output_init(const struct config_param *param, GError **error_r)
-{
- struct pulse_output *po;
-
- g_setenv("PULSE_PROP_media.role", "music", true);
-
- po = g_new(struct pulse_output, 1);
- if (!ao_base_init(&po->base, &pulse_output_plugin, param, error_r)) {
- g_free(po);
- return NULL;
- }
-
- po->name = config_get_block_string(param, "name", "mpd_pulse");
- po->server = config_get_block_string(param, "server", NULL);
- po->sink = config_get_block_string(param, "sink", NULL);
-
- po->mixer = NULL;
- po->mainloop = NULL;
- po->context = NULL;
- po->stream = NULL;
-
- return &po->base;
-}
-
-static void
-pulse_output_finish(struct audio_output *ao)
-{
- struct pulse_output *po = (struct pulse_output *)ao;
-
- ao_base_finish(&po->base);
- g_free(po);
-}
-
-static bool
-pulse_output_enable(struct audio_output *ao, GError **error_r)
-{
- struct pulse_output *po = (struct pulse_output *)ao;
-
- assert(po->mainloop == NULL);
- assert(po->context == NULL);
-
- /* create the libpulse mainloop and start the thread */
-
- po->mainloop = pa_threaded_mainloop_new();
- if (po->mainloop == NULL) {
- g_free(po);
-
- g_set_error(error_r, pulse_output_quark(), 0,
- "pa_threaded_mainloop_new() has failed");
- return false;
- }
-
- pa_threaded_mainloop_lock(po->mainloop);
-
- if (pa_threaded_mainloop_start(po->mainloop) < 0) {
- pa_threaded_mainloop_unlock(po->mainloop);
- pa_threaded_mainloop_free(po->mainloop);
- po->mainloop = NULL;
-
- g_set_error(error_r, pulse_output_quark(), 0,
- "pa_threaded_mainloop_start() has failed");
- return false;
- }
-
- /* create the libpulse context and connect it */
-
- if (!pulse_output_setup_context(po, error_r)) {
- pa_threaded_mainloop_unlock(po->mainloop);
- pa_threaded_mainloop_stop(po->mainloop);
- pa_threaded_mainloop_free(po->mainloop);
- po->mainloop = NULL;
- return false;
- }
-
- pa_threaded_mainloop_unlock(po->mainloop);
-
- return true;
-}
-
-static void
-pulse_output_disable(struct audio_output *ao)
-{
- struct pulse_output *po = (struct pulse_output *)ao;
-
- assert(po->mainloop != NULL);
-
- pa_threaded_mainloop_stop(po->mainloop);
- if (po->context != NULL)
- pulse_output_delete_context(po);
- pa_threaded_mainloop_free(po->mainloop);
- po->mainloop = NULL;
-}
-
-/**
- * Check if the context is (already) connected, and waits if not. If
- * the context has been disconnected, retry to connect.
- *
- * Caller must lock the main loop.
- *
- * @return true on success, false on error
- */
-static bool
-pulse_output_wait_connection(struct pulse_output *po, GError **error_r)
-{
- assert(po->mainloop != NULL);
-
- pa_context_state_t state;
-
- if (po->context == NULL && !pulse_output_setup_context(po, error_r))
- return false;
-
- while (true) {
- state = pa_context_get_state(po->context);
- switch (state) {
- case PA_CONTEXT_READY:
- /* nothing to do */
- return true;
-
- case PA_CONTEXT_UNCONNECTED:
- case PA_CONTEXT_TERMINATED:
- case PA_CONTEXT_FAILED:
- /* failure */
- g_set_error(error_r, pulse_output_quark(), 0,
- "failed to connect: %s",
- pa_strerror(pa_context_errno(po->context)));
- pulse_output_delete_context(po);
- return false;
-
- case PA_CONTEXT_CONNECTING:
- case PA_CONTEXT_AUTHORIZING:
- case PA_CONTEXT_SETTING_NAME:
- /* wait some more */
- pa_threaded_mainloop_wait(po->mainloop);
- break;
- }
- }
-}
-
-#if PA_CHECK_VERSION(0,9,8)
-
-static void
-pulse_output_stream_suspended_cb(G_GNUC_UNUSED pa_stream *stream, void *userdata)
-{
- struct pulse_output *po = userdata;
-
- assert(stream == po->stream || po->stream == NULL);
- assert(po->mainloop != NULL);
-
- /* wake up the main loop to break out of the loop in
- pulse_output_play() */
- pa_threaded_mainloop_signal(po->mainloop, 0);
-}
-
-#endif
-
-static void
-pulse_output_stream_state_cb(pa_stream *stream, void *userdata)
-{
- struct pulse_output *po = userdata;
-
- assert(stream == po->stream || po->stream == NULL);
- assert(po->mainloop != NULL);
- assert(po->context != NULL);
-
- switch (pa_stream_get_state(stream)) {
- case PA_STREAM_READY:
- if (po->mixer != NULL)
- pulse_mixer_on_change(po->mixer, po->context, stream);
-
- pa_threaded_mainloop_signal(po->mainloop, 0);
- break;
-
- case PA_STREAM_FAILED:
- case PA_STREAM_TERMINATED:
- if (po->mixer != NULL)
- pulse_mixer_on_disconnect(po->mixer);
-
- pa_threaded_mainloop_signal(po->mainloop, 0);
- break;
-
- case PA_STREAM_UNCONNECTED:
- case PA_STREAM_CREATING:
- break;
- }
-}
-
-static void
-pulse_output_stream_write_cb(G_GNUC_UNUSED pa_stream *stream, size_t nbytes,
- void *userdata)
-{
- struct pulse_output *po = userdata;
-
- assert(po->mainloop != NULL);
-
- po->writable = nbytes;
- pa_threaded_mainloop_signal(po->mainloop, 0);
-}
-
-/**
- * Create, set up and connect a context.
- *
- * Caller must lock the main loop.
- *
- * @return true on success, false on error
- */
-static bool
-pulse_output_setup_stream(struct pulse_output *po, const pa_sample_spec *ss,
- GError **error_r)
-{
- assert(po != NULL);
- assert(po->context != NULL);
-
- po->stream = pa_stream_new(po->context, po->name, ss, NULL);
- if (po->stream == NULL) {
- g_set_error(error_r, pulse_output_quark(), 0,
- "pa_stream_new() has failed: %s",
- pa_strerror(pa_context_errno(po->context)));
- return false;
- }
-
-#if PA_CHECK_VERSION(0,9,8)
- pa_stream_set_suspended_callback(po->stream,
- pulse_output_stream_suspended_cb, po);
-#endif
-
- pa_stream_set_state_callback(po->stream,
- pulse_output_stream_state_cb, po);
- pa_stream_set_write_callback(po->stream,
- pulse_output_stream_write_cb, po);
-
- return true;
-}
-
-static bool
-pulse_output_open(struct audio_output *ao, struct audio_format *audio_format,
- GError **error_r)
-{
- struct pulse_output *po = (struct pulse_output *)ao;
- pa_sample_spec ss;
- int error;
-
- assert(po->mainloop != NULL);
-
- pa_threaded_mainloop_lock(po->mainloop);
-
- if (po->context != NULL) {
- switch (pa_context_get_state(po->context)) {
- case PA_CONTEXT_UNCONNECTED:
- case PA_CONTEXT_TERMINATED:
- case PA_CONTEXT_FAILED:
- /* the connection was closed meanwhile; delete
- it, and pulse_output_wait_connection() will
- reopen it */
- pulse_output_delete_context(po);
- break;
-
- case PA_CONTEXT_READY:
- case PA_CONTEXT_CONNECTING:
- case PA_CONTEXT_AUTHORIZING:
- case PA_CONTEXT_SETTING_NAME:
- break;
- }
- }
-
- if (!pulse_output_wait_connection(po, error_r)) {
- pa_threaded_mainloop_unlock(po->mainloop);
- return false;
- }
-
- /* MPD doesn't support the other pulseaudio sample formats, so
- we just force MPD to send us everything as 16 bit */
- audio_format->format = SAMPLE_FORMAT_S16;
-
- ss.format = PA_SAMPLE_S16NE;
- ss.rate = audio_format->sample_rate;
- ss.channels = audio_format->channels;
-
- /* create a stream .. */
-
- if (!pulse_output_setup_stream(po, &ss, error_r)) {
- pa_threaded_mainloop_unlock(po->mainloop);
- return false;
- }
-
- /* .. and connect it (asynchronously) */
-
- error = pa_stream_connect_playback(po->stream, po->sink,
- NULL, 0, NULL, NULL);
- if (error < 0) {
- pulse_output_delete_stream(po);
-
- g_set_error(error_r, pulse_output_quark(), 0,
- "pa_stream_connect_playback() has failed: %s",
- pa_strerror(pa_context_errno(po->context)));
- pa_threaded_mainloop_unlock(po->mainloop);
- return false;
- }
-
- pa_threaded_mainloop_unlock(po->mainloop);
-
-#if !PA_CHECK_VERSION(0,9,11)
- po->pause = false;
-#endif
-
- return true;
-}
-
-static void
-pulse_output_close(struct audio_output *ao)
-{
- struct pulse_output *po = (struct pulse_output *)ao;
- pa_operation *o;
-
- assert(po->mainloop != NULL);
-
- pa_threaded_mainloop_lock(po->mainloop);
-
- if (pa_stream_get_state(po->stream) == PA_STREAM_READY) {
- o = pa_stream_drain(po->stream,
- pulse_output_stream_success_cb, po);
- if (o == NULL) {
- g_warning("pa_stream_drain() has failed: %s",
- pa_strerror(pa_context_errno(po->context)));
- } else
- pulse_wait_for_operation(po->mainloop, o);
- }
-
- pulse_output_delete_stream(po);
-
- if (po->context != NULL &&
- pa_context_get_state(po->context) != PA_CONTEXT_READY)
- pulse_output_delete_context(po);
-
- pa_threaded_mainloop_unlock(po->mainloop);
-}
-
-/**
- * Check if the stream is (already) connected, and waits if not. The
- * mainloop must be locked before calling this function.
- *
- * @return true on success, false on error
- */
-static bool
-pulse_output_wait_stream(struct pulse_output *po, GError **error_r)
-{
- while (true) {
- switch (pa_stream_get_state(po->stream)) {
- case PA_STREAM_READY:
- return true;
-
- case PA_STREAM_FAILED:
- case PA_STREAM_TERMINATED:
- case PA_STREAM_UNCONNECTED:
- g_set_error(error_r, pulse_output_quark(),
- pa_context_errno(po->context),
- "failed to connect the stream: %s",
- pa_strerror(pa_context_errno(po->context)));
- return false;
-
- case PA_STREAM_CREATING:
- pa_threaded_mainloop_wait(po->mainloop);
- break;
- }
- }
-}
-
-/**
- * Determines whether the stream is paused. On libpulse older than
- * 0.9.11, it uses a custom pause flag.
- */
-static bool
-pulse_output_stream_is_paused(struct pulse_output *po)
-{
- assert(po->stream != NULL);
-
-#if !defined(PA_CHECK_VERSION) || !PA_CHECK_VERSION(0,9,11)
- return po->pause;
-#else
- return pa_stream_is_corked(po->stream);
-#endif
-}
-
-/**
- * Sets cork mode on the stream.
- */
-static bool
-pulse_output_stream_pause(struct pulse_output *po, bool pause,
- GError **error_r)
-{
- pa_operation *o;
-
- assert(po->mainloop != NULL);
- assert(po->context != NULL);
- assert(po->stream != NULL);
-
- o = pa_stream_cork(po->stream, pause,
- pulse_output_stream_success_cb, po);
- if (o == NULL) {
- g_set_error(error_r, pulse_output_quark(), 0,
- "pa_stream_cork() has failed: %s",
- pa_strerror(pa_context_errno(po->context)));
- return false;
- }
-
- if (!pulse_wait_for_operation(po->mainloop, o)) {
- g_set_error(error_r, pulse_output_quark(), 0,
- "pa_stream_cork() has failed: %s",
- pa_strerror(pa_context_errno(po->context)));
- return false;
- }
-
-#if !PA_CHECK_VERSION(0,9,11)
- po->pause = pause;
-#endif
- return true;
-}
-
-static unsigned
-pulse_output_delay(struct audio_output *ao)
-{
- struct pulse_output *po = (struct pulse_output *)ao;
- unsigned result = 0;
-
- pa_threaded_mainloop_lock(po->mainloop);
-
- if (po->base.pause && pulse_output_stream_is_paused(po) &&
- pa_stream_get_state(po->stream) == PA_STREAM_READY)
- /* idle while paused */
- result = 1000;
-
- pa_threaded_mainloop_unlock(po->mainloop);
-
- return result;
-}
-
-static size_t
-pulse_output_play(struct audio_output *ao, const void *chunk, size_t size,
- GError **error_r)
-{
- struct pulse_output *po = (struct pulse_output *)ao;
- int error;
-
- assert(po->mainloop != NULL);
- assert(po->stream != NULL);
-
- pa_threaded_mainloop_lock(po->mainloop);
-
- /* check if the stream is (already) connected */
-
- if (!pulse_output_wait_stream(po, error_r)) {
- pa_threaded_mainloop_unlock(po->mainloop);
- return 0;
- }
-
- assert(po->context != NULL);
-
- /* unpause if previously paused */
-
- if (pulse_output_stream_is_paused(po) &&
- !pulse_output_stream_pause(po, false, error_r)) {
- pa_threaded_mainloop_unlock(po->mainloop);
- return 0;
- }
-
- /* wait until the server allows us to write */
-
- while (po->writable == 0) {
-#if PA_CHECK_VERSION(0,9,8)
- if (pa_stream_is_suspended(po->stream)) {
- pa_threaded_mainloop_unlock(po->mainloop);
- g_set_error(error_r, pulse_output_quark(), 0,
- "suspended");
- return 0;
- }
-#endif
-
- pa_threaded_mainloop_wait(po->mainloop);
-
- if (pa_stream_get_state(po->stream) != PA_STREAM_READY) {
- pa_threaded_mainloop_unlock(po->mainloop);
- g_set_error(error_r, pulse_output_quark(), 0,
- "disconnected");
- return 0;
- }
- }
-
- /* now write */
-
- if (size > po->writable)
- /* don't send more than possible */
- size = po->writable;
-
- po->writable -= size;
-
- error = pa_stream_write(po->stream, chunk, size, NULL,
- 0, PA_SEEK_RELATIVE);
- pa_threaded_mainloop_unlock(po->mainloop);
- if (error < 0) {
- g_set_error(error_r, pulse_output_quark(), error,
- "%s", pa_strerror(error));
- return 0;
- }
-
- return size;
-}
-
-static void
-pulse_output_cancel(struct audio_output *ao)
-{
- struct pulse_output *po = (struct pulse_output *)ao;
- pa_operation *o;
-
- assert(po->mainloop != NULL);
- assert(po->stream != NULL);
-
- pa_threaded_mainloop_lock(po->mainloop);
-
- if (pa_stream_get_state(po->stream) != PA_STREAM_READY) {
- /* no need to flush when the stream isn't connected
- yet */
- pa_threaded_mainloop_unlock(po->mainloop);
- return;
- }
-
- assert(po->context != NULL);
-
- o = pa_stream_flush(po->stream, pulse_output_stream_success_cb, po);
- if (o == NULL) {
- g_warning("pa_stream_flush() has failed: %s",
- pa_strerror(pa_context_errno(po->context)));
- pa_threaded_mainloop_unlock(po->mainloop);
- return;
- }
-
- pulse_wait_for_operation(po->mainloop, o);
- pa_threaded_mainloop_unlock(po->mainloop);
-}
-
-static bool
-pulse_output_pause(struct audio_output *ao)
-{
- struct pulse_output *po = (struct pulse_output *)ao;
- GError *error = NULL;
-
- assert(po->mainloop != NULL);
- assert(po->stream != NULL);
-
- pa_threaded_mainloop_lock(po->mainloop);
-
- /* check if the stream is (already/still) connected */
-
- if (!pulse_output_wait_stream(po, &error)) {
- pa_threaded_mainloop_unlock(po->mainloop);
- g_warning("%s", error->message);
- g_error_free(error);
- return false;
- }
-
- assert(po->context != NULL);
-
- /* cork the stream */
-
- if (!pulse_output_stream_is_paused(po) &&
- !pulse_output_stream_pause(po, true, &error)) {
- pa_threaded_mainloop_unlock(po->mainloop);
- g_warning("%s", error->message);
- g_error_free(error);
- return false;
- }
-
- pa_threaded_mainloop_unlock(po->mainloop);
-
- return true;
-}
-
-static bool
-pulse_output_test_default_device(void)
-{
- struct pulse_output *po;
- bool success;
-
- po = (struct pulse_output *)pulse_output_init(NULL, NULL);
- if (po == NULL)
- return false;
-
- success = pulse_output_wait_connection(po, NULL);
- pulse_output_finish(&po->base);
-
- return success;
-}
-
-const struct audio_output_plugin pulse_output_plugin = {
- .name = "pulse",
-
- .test_default_device = pulse_output_test_default_device,
- .init = pulse_output_init,
- .finish = pulse_output_finish,
- .enable = pulse_output_enable,
- .disable = pulse_output_disable,
- .open = pulse_output_open,
- .delay = pulse_output_delay,
- .play = pulse_output_play,
- .cancel = pulse_output_cancel,
- .pause = pulse_output_pause,
- .close = pulse_output_close,
-
- .mixer_plugin = &pulse_mixer_plugin,
-};
diff --git a/src/output/pulse_output_plugin.h b/src/output/pulse_output_plugin.h
deleted file mode 100644
index 02a51f27b..000000000
--- a/src/output/pulse_output_plugin.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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_PULSE_OUTPUT_PLUGIN_H
-#define MPD_PULSE_OUTPUT_PLUGIN_H
-
-#include <stdbool.h>
-
-#include <glib.h>
-
-struct pulse_output;
-struct pulse_mixer;
-struct pa_cvolume;
-
-extern const struct audio_output_plugin pulse_output_plugin;
-
-void
-pulse_output_lock(struct pulse_output *po);
-
-void
-pulse_output_unlock(struct pulse_output *po);
-
-void
-pulse_output_set_mixer(struct pulse_output *po, struct pulse_mixer *pm);
-
-void
-pulse_output_clear_mixer(struct pulse_output *po, struct pulse_mixer *pm);
-
-bool
-pulse_output_set_volume(struct pulse_output *po,
- const struct pa_cvolume *volume, GError **error_r);
-
-#endif
diff --git a/src/output/recorder_output_plugin.c b/src/output/recorder_output_plugin.c
deleted file mode 100644
index b84cb244c..000000000
--- a/src/output/recorder_output_plugin.c
+++ /dev/null
@@ -1,251 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "recorder_output_plugin.h"
-#include "output_api.h"
-#include "encoder_plugin.h"
-#include "encoder_list.h"
-#include "fd_util.h"
-#include "open.h"
-
-#include <assert.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <errno.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "recorder"
-
-struct recorder_output {
- struct audio_output base;
-
- /**
- * The configured encoder plugin.
- */
- struct encoder *encoder;
-
- /**
- * The destination file name.
- */
- const char *path;
-
- /**
- * The destination file descriptor.
- */
- int fd;
-
- /**
- * The buffer for encoder_read().
- */
- char buffer[32768];
-};
-
-/**
- * The quark used for GError.domain.
- */
-static inline GQuark
-recorder_output_quark(void)
-{
- return g_quark_from_static_string("recorder_output");
-}
-
-static struct audio_output *
-recorder_output_init(const struct config_param *param, GError **error_r)
-{
- struct recorder_output *recorder = g_new(struct recorder_output, 1);
- if (!ao_base_init(&recorder->base, &recorder_output_plugin, param,
- error_r)) {
- g_free(recorder);
- return NULL;
- }
-
- /* read configuration */
-
- const char *encoder_name =
- config_get_block_string(param, "encoder", "vorbis");
- const struct encoder_plugin *encoder_plugin =
- encoder_plugin_get(encoder_name);
- if (encoder_plugin == NULL) {
- g_set_error(error_r, recorder_output_quark(), 0,
- "No such encoder: %s", encoder_name);
- goto failure;
- }
-
- recorder->path = config_get_block_string(param, "path", NULL);
- if (recorder->path == NULL) {
- g_set_error(error_r, recorder_output_quark(), 0,
- "'path' not configured");
- goto failure;
- }
-
- /* initialize encoder */
-
- recorder->encoder = encoder_init(encoder_plugin, param, error_r);
- if (recorder->encoder == NULL)
- goto failure;
-
- return &recorder->base;
-
-failure:
- ao_base_finish(&recorder->base);
- g_free(recorder);
- return NULL;
-}
-
-static void
-recorder_output_finish(struct audio_output *ao)
-{
- struct recorder_output *recorder = (struct recorder_output *)ao;
-
- encoder_finish(recorder->encoder);
- ao_base_finish(&recorder->base);
- g_free(recorder);
-}
-
-static bool
-recorder_write_to_file(struct recorder_output *recorder,
- const void *_data, size_t length,
- GError **error_r)
-{
- assert(length > 0);
-
- const int fd = recorder->fd;
-
- const uint8_t *data = (const uint8_t *)_data, *end = data + length;
-
- while (true) {
- ssize_t nbytes = write(fd, data, end - data);
- if (nbytes > 0) {
- data += nbytes;
- if (data == end)
- return true;
- } else if (nbytes == 0) {
- /* shouldn't happen for files */
- g_set_error(error_r, recorder_output_quark(), 0,
- "write() returned 0");
- return false;
- } else if (errno != EINTR) {
- g_set_error(error_r, recorder_output_quark(), 0,
- "Failed to write to '%s': %s",
- recorder->path, g_strerror(errno));
- return false;
- }
- }
-}
-
-/**
- * Writes pending data from the encoder to the output file.
- */
-static bool
-recorder_output_encoder_to_file(struct recorder_output *recorder,
- GError **error_r)
-{
- assert(recorder->fd >= 0);
-
- while (true) {
- /* read from the encoder */
-
- size_t size = encoder_read(recorder->encoder, recorder->buffer,
- sizeof(recorder->buffer));
- if (size == 0)
- return true;
-
- /* write everything into the file */
-
- if (!recorder_write_to_file(recorder, recorder->buffer, size,
- error_r))
- return false;
- }
-}
-
-static bool
-recorder_output_open(struct audio_output *ao,
- struct audio_format *audio_format,
- GError **error_r)
-{
- struct recorder_output *recorder = (struct recorder_output *)ao;
-
- /* create the output file */
-
- recorder->fd = open_cloexec(recorder->path,
- O_CREAT|O_WRONLY|O_TRUNC|O_BINARY,
- 0666);
- if (recorder->fd < 0) {
- g_set_error(error_r, recorder_output_quark(), 0,
- "Failed to create '%s': %s",
- recorder->path, g_strerror(errno));
- return false;
- }
-
- /* open the encoder */
-
- if (!encoder_open(recorder->encoder, audio_format, error_r)) {
- close(recorder->fd);
- unlink(recorder->path);
- return false;
- }
-
- if (!recorder_output_encoder_to_file(recorder, error_r)) {
- encoder_close(recorder->encoder);
- close(recorder->fd);
- unlink(recorder->path);
- return false;
- }
-
- return true;
-}
-
-static void
-recorder_output_close(struct audio_output *ao)
-{
- struct recorder_output *recorder = (struct recorder_output *)ao;
-
- /* flush the encoder and write the rest to the file */
-
- if (encoder_end(recorder->encoder, NULL))
- recorder_output_encoder_to_file(recorder, NULL);
-
- /* now really close everything */
-
- encoder_close(recorder->encoder);
-
- close(recorder->fd);
-}
-
-static size_t
-recorder_output_play(struct audio_output *ao, const void *chunk, size_t size,
- GError **error_r)
-{
- struct recorder_output *recorder = (struct recorder_output *)ao;
-
- return encoder_write(recorder->encoder, chunk, size, error_r) &&
- recorder_output_encoder_to_file(recorder, error_r)
- ? size : 0;
-}
-
-const struct audio_output_plugin recorder_output_plugin = {
- .name = "recorder",
- .init = recorder_output_init,
- .finish = recorder_output_finish,
- .open = recorder_output_open,
- .close = recorder_output_close,
- .play = recorder_output_play,
-};
diff --git a/src/output/recorder_output_plugin.h b/src/output/recorder_output_plugin.h
deleted file mode 100644
index a9bf755bd..000000000
--- a/src/output/recorder_output_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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_RECORDER_OUTPUT_PLUGIN_H
-#define MPD_RECORDER_OUTPUT_PLUGIN_H
-
-extern const struct audio_output_plugin recorder_output_plugin;
-
-#endif
diff --git a/src/output/roar_output_plugin.c b/src/output/roar_output_plugin.c
deleted file mode 100644
index 1c2c48321..000000000
--- a/src/output/roar_output_plugin.c
+++ /dev/null
@@ -1,401 +0,0 @@
-/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
- * Copyright (C) 2010-2011 Philipp 'ph3-der-loewe' Schafft
- * Copyright (C) 2010-2011 Hans-Kristian 'maister' Arntzen
- *
- * 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 "roar_output_plugin.h"
-#include "output_api.h"
-#include "mixer_list.h"
-#include "roar_output_plugin.h"
-
-#include <glib.h>
-#include <stdint.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdint.h>
-
-#include <roaraudio.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "roaraudio"
-
-typedef struct roar
-{
- struct audio_output base;
-
- roar_vs_t * vss;
- int err;
- char *host;
- char *name;
- int role;
- struct roar_connection con;
- struct roar_audio_info info;
- GMutex *lock;
- volatile bool alive;
-} roar_t;
-
-static inline GQuark
-roar_output_quark(void)
-{
- return g_quark_from_static_string("roar_output");
-}
-
-static int
-roar_output_get_volume_locked(struct roar *roar)
-{
- if (roar->vss == NULL || !roar->alive)
- return -1;
-
- float l, r;
- int error;
- if (roar_vs_volume_get(roar->vss, &l, &r, &error) < 0)
- return -1;
-
- return (l + r) * 50;
-}
-
-int
-roar_output_get_volume(struct roar *roar)
-{
- g_mutex_lock(roar->lock);
- int volume = roar_output_get_volume_locked(roar);
- g_mutex_unlock(roar->lock);
- return volume;
-}
-
-static bool
-roar_output_set_volume_locked(struct roar *roar, unsigned volume)
-{
- assert(volume <= 100);
-
- if (roar->vss == NULL || !roar->alive)
- return false;
-
- int error;
- float level = volume / 100.0;
-
- roar_vs_volume_mono(roar->vss, level, &error);
- return true;
-}
-
-bool
-roar_output_set_volume(struct roar *roar, unsigned volume)
-{
- g_mutex_lock(roar->lock);
- bool success = roar_output_set_volume_locked(roar, volume);
- g_mutex_unlock(roar->lock);
- return success;
-}
-
-static void
-roar_configure(struct roar * self, const struct config_param *param)
-{
- self->host = config_dup_block_string(param, "server", NULL);
- self->name = config_dup_block_string(param, "name", "MPD");
-
- const char *role = config_get_block_string(param, "role", "music");
- self->role = role != NULL
- ? roar_str2role(role)
- : ROAR_ROLE_MUSIC;
-}
-
-static struct audio_output *
-roar_init(const struct config_param *param, GError **error_r)
-{
- struct roar *self = g_new0(struct roar, 1);
-
- if (!ao_base_init(&self->base, &roar_output_plugin, param, error_r)) {
- g_free(self);
- return NULL;
- }
-
- self->lock = g_mutex_new();
- self->err = ROAR_ERROR_NONE;
- roar_configure(self, param);
- return &self->base;
-}
-
-static void
-roar_finish(struct audio_output *ao)
-{
- struct roar *self = (struct roar *)ao;
-
- g_free(self->host);
- g_free(self->name);
- g_mutex_free(self->lock);
-
- ao_base_finish(&self->base);
- g_free(self);
-}
-
-static void
-roar_use_audio_format(struct roar_audio_info *info,
- struct audio_format *audio_format)
-{
- info->rate = audio_format->sample_rate;
- info->channels = audio_format->channels;
- info->codec = ROAR_CODEC_PCM_S;
-
- switch (audio_format->format) {
- case SAMPLE_FORMAT_UNDEFINED:
- info->bits = 16;
- audio_format->format = SAMPLE_FORMAT_S16;
- break;
-
- case SAMPLE_FORMAT_S8:
- info->bits = 8;
- break;
-
- case SAMPLE_FORMAT_S16:
- info->bits = 16;
- break;
-
- case SAMPLE_FORMAT_S24_P32:
- info->bits = 32;
- audio_format->format = SAMPLE_FORMAT_S32;
- break;
-
- case SAMPLE_FORMAT_S32:
- info->bits = 32;
- break;
- }
-}
-
-static bool
-roar_open(struct audio_output *ao, struct audio_format *audio_format, GError **error)
-{
- struct roar *self = (struct roar *)ao;
- g_mutex_lock(self->lock);
-
- if (roar_simple_connect(&(self->con), self->host, self->name) < 0)
- {
- g_set_error(error, roar_output_quark(), 0,
- "Failed to connect to Roar server");
- g_mutex_unlock(self->lock);
- return false;
- }
-
- self->vss = roar_vs_new_from_con(&(self->con), &(self->err));
-
- if (self->vss == NULL || self->err != ROAR_ERROR_NONE)
- {
- g_set_error(error, roar_output_quark(), 0,
- "Failed to connect to server");
- g_mutex_unlock(self->lock);
- return false;
- }
-
- roar_use_audio_format(&self->info, audio_format);
-
- if (roar_vs_stream(self->vss, &(self->info), ROAR_DIR_PLAY,
- &(self->err)) < 0)
- {
- g_set_error(error, roar_output_quark(), 0, "Failed to start stream");
- g_mutex_unlock(self->lock);
- return false;
- }
- roar_vs_role(self->vss, self->role, &(self->err));
- self->alive = true;
-
- g_mutex_unlock(self->lock);
- return true;
-}
-
-static void
-roar_close(struct audio_output *ao)
-{
- struct roar *self = (struct roar *)ao;
- g_mutex_lock(self->lock);
- self->alive = false;
-
- if (self->vss != NULL)
- roar_vs_close(self->vss, ROAR_VS_TRUE, &(self->err));
- self->vss = NULL;
- roar_disconnect(&(self->con));
- g_mutex_unlock(self->lock);
-}
-
-static void
-roar_cancel_locked(struct roar *self)
-{
- if (self->vss == NULL)
- return;
-
- roar_vs_t *vss = self->vss;
- self->vss = NULL;
- roar_vs_close(vss, ROAR_VS_TRUE, &(self->err));
- self->alive = false;
-
- vss = roar_vs_new_from_con(&(self->con), &(self->err));
- if (vss == NULL)
- return;
-
- if (roar_vs_stream(vss, &(self->info), ROAR_DIR_PLAY,
- &(self->err)) < 0) {
- roar_vs_close(vss, ROAR_VS_TRUE, &(self->err));
- g_warning("Failed to start stream");
- return;
- }
-
- roar_vs_role(vss, self->role, &(self->err));
- self->vss = vss;
- self->alive = true;
-}
-
-static void
-roar_cancel(struct audio_output *ao)
-{
- struct roar *self = (struct roar *)ao;
-
- g_mutex_lock(self->lock);
- roar_cancel_locked(self);
- g_mutex_unlock(self->lock);
-}
-
-static size_t
-roar_play(struct audio_output *ao, const void *chunk, size_t size, GError **error)
-{
- struct roar *self = (struct roar *)ao;
- ssize_t rc;
-
- if (self->vss == NULL)
- {
- g_set_error(error, roar_output_quark(), 0, "Connection is invalid");
- return 0;
- }
-
- rc = roar_vs_write(self->vss, chunk, size, &(self->err));
- if ( rc <= 0 )
- {
- g_set_error(error, roar_output_quark(), 0, "Failed to play data");
- return 0;
- }
-
- return rc;
-}
-
-static const char*
-roar_tag_convert(enum tag_type type, bool *is_uuid)
-{
- *is_uuid = false;
- switch (type)
- {
- case TAG_ARTIST:
- case TAG_ALBUM_ARTIST:
- return "AUTHOR";
- case TAG_ALBUM:
- return "ALBUM";
- case TAG_TITLE:
- return "TITLE";
- case TAG_TRACK:
- return "TRACK";
- case TAG_NAME:
- return "NAME";
- case TAG_GENRE:
- return "GENRE";
- case TAG_DATE:
- return "DATE";
- case TAG_PERFORMER:
- return "PERFORMER";
- case TAG_COMMENT:
- return "COMMENT";
- case TAG_DISC:
- return "DISCID";
- case TAG_COMPOSER:
-#ifdef ROAR_META_TYPE_COMPOSER
- return "COMPOSER";
-#else
- return "AUTHOR";
-#endif
- case TAG_MUSICBRAINZ_ARTISTID:
- case TAG_MUSICBRAINZ_ALBUMID:
- case TAG_MUSICBRAINZ_ALBUMARTISTID:
- case TAG_MUSICBRAINZ_TRACKID:
- *is_uuid = true;
- return "HASH";
-
- default:
- return NULL;
- }
-}
-
-static void
-roar_send_tag(struct audio_output *ao, const struct tag *meta)
-{
- struct roar *self = (struct roar *)ao;
-
- if (self->vss == NULL)
- return;
-
- g_mutex_lock(self->lock);
- size_t cnt = 1;
- struct roar_keyval vals[32];
- memset(vals, 0, sizeof(vals));
- char uuid_buf[32][64];
-
- char timebuf[16];
- snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d",
- meta->time / 3600, (meta->time % 3600) / 60, meta->time % 60);
-
- vals[0].key = g_strdup("LENGTH");
- vals[0].value = timebuf;
-
- for (unsigned i = 0; i < meta->num_items && cnt < 32; i++)
- {
- bool is_uuid = false;
- const char *key = roar_tag_convert(meta->items[i]->type, &is_uuid);
- if (key != NULL)
- {
- if (is_uuid)
- {
- snprintf(uuid_buf[cnt], sizeof(uuid_buf[0]), "{UUID}%s",
- meta->items[i]->value);
- vals[cnt].key = g_strdup(key);
- vals[cnt].value = uuid_buf[cnt];
- }
- else
- {
- vals[cnt].key = g_strdup(key);
- vals[cnt].value = meta->items[i]->value;
- }
- cnt++;
- }
- }
-
- roar_vs_meta(self->vss, vals, cnt, &(self->err));
-
- for (unsigned i = 0; i < 32; i++)
- g_free(vals[i].key);
-
- g_mutex_unlock(self->lock);
-}
-
-const struct audio_output_plugin roar_output_plugin = {
- .name = "roar",
- .init = roar_init,
- .finish = roar_finish,
- .open = roar_open,
- .play = roar_play,
- .cancel = roar_cancel,
- .close = roar_close,
- .send_tag = roar_send_tag,
-
- .mixer_plugin = &roar_mixer_plugin
-};
diff --git a/src/output/roar_output_plugin.h b/src/output/roar_output_plugin.h
deleted file mode 100644
index 78b628cc4..000000000
--- a/src/output/roar_output_plugin.h
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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_ROAR_OUTPUT_PLUGIN_H
-#define MPD_ROAR_OUTPUT_PLUGIN_H
-
-#include <stdbool.h>
-
-struct roar;
-
-extern const struct audio_output_plugin roar_output_plugin;
-
-int
-roar_output_get_volume(struct roar *roar);
-
-bool
-roar_output_set_volume(struct roar *roar, unsigned volume);
-
-#endif
diff --git a/src/output/shout_output_plugin.c b/src/output/shout_output_plugin.c
deleted file mode 100644
index 56456a0ea..000000000
--- a/src/output/shout_output_plugin.c
+++ /dev/null
@@ -1,555 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "shout_output_plugin.h"
-#include "output_api.h"
-#include "encoder_plugin.h"
-#include "encoder_list.h"
-#include "mpd_error.h"
-
-#include <shout/shout.h>
-#include <glib.h>
-
-#include <assert.h>
-#include <stdlib.h>
-#include <stdio.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "shout"
-
-#define DEFAULT_CONN_TIMEOUT 2
-
-struct shout_data {
- struct audio_output base;
-
- shout_t *shout_conn;
- shout_metadata_t *shout_meta;
-
- struct encoder *encoder;
-
- float quality;
- int bitrate;
-
- int timeout;
-
- uint8_t buffer[32768];
-};
-
-static int shout_init_count;
-
-/**
- * The quark used for GError.domain.
- */
-static inline GQuark
-shout_output_quark(void)
-{
- return g_quark_from_static_string("shout_output");
-}
-
-static const struct encoder_plugin *
-shout_encoder_plugin_get(const char *name)
-{
- if (strcmp(name, "ogg") == 0)
- name = "vorbis";
- else if (strcmp(name, "mp3") == 0)
- name = "lame";
-
- return encoder_plugin_get(name);
-}
-
-static struct shout_data *new_shout_data(void)
-{
- struct shout_data *ret = g_new(struct shout_data, 1);
-
- ret->shout_conn = shout_new();
- ret->shout_meta = shout_metadata_new();
- ret->bitrate = -1;
- ret->quality = -2.0;
- ret->timeout = DEFAULT_CONN_TIMEOUT;
-
- return ret;
-}
-
-static void free_shout_data(struct shout_data *sd)
-{
- if (sd->shout_meta)
- shout_metadata_free(sd->shout_meta);
- if (sd->shout_conn)
- shout_free(sd->shout_conn);
-
- g_free(sd);
-}
-
-#define check_block_param(name) { \
- block_param = config_get_block_param(param, name); \
- if (!block_param) { \
- MPD_ERROR("no \"%s\" defined for shout device defined at line " \
- "%i\n", name, param->line); \
- } \
- }
-
-static struct audio_output *
-my_shout_init_driver(const struct config_param *param,
- GError **error)
-{
- struct shout_data *sd = new_shout_data();
- if (!ao_base_init(&sd->base, &shout_output_plugin, param, error)) {
- free_shout_data(sd);
- return NULL;
- }
-
- const struct audio_format *audio_format =
- &sd->base.config_audio_format;
- if (!audio_format_fully_defined(audio_format)) {
- g_set_error(error, shout_output_quark(), 0,
- "Need full audio format specification");
- ao_base_finish(&sd->base);
- free_shout_data(sd);
- return NULL;
- }
-
- if (shout_init_count == 0)
- shout_init();
-
- shout_init_count++;
-
- const struct block_param *block_param;
- check_block_param("host");
- char *host = block_param->value;
-
- check_block_param("mount");
- char *mount = block_param->value;
-
- unsigned port = config_get_block_unsigned(param, "port", 0);
- if (port == 0) {
- g_set_error(error, shout_output_quark(), 0,
- "shout port must be configured");
- goto failure;
- }
-
- check_block_param("password");
- const char *passwd = block_param->value;
-
- check_block_param("name");
- const char *name = block_param->value;
-
- bool public = config_get_block_bool(param, "public", false);
-
- const char *user = config_get_block_string(param, "user", "source");
-
- const char *value = config_get_block_string(param, "quality", NULL);
- if (value != NULL) {
- char *test;
- sd->quality = strtod(value, &test);
-
- if (*test != '\0' || sd->quality < -1.0 || sd->quality > 10.0) {
- g_set_error(error, shout_output_quark(), 0,
- "shout quality \"%s\" is not a number in the "
- "range -1 to 10, line %i",
- value, param->line);
- goto failure;
- }
-
- if (config_get_block_string(param, "bitrate", NULL) != NULL) {
- g_set_error(error, shout_output_quark(), 0,
- "quality and bitrate are "
- "both defined");
- goto failure;
- }
- } else {
- value = config_get_block_string(param, "bitrate", NULL);
- if (value == NULL) {
- g_set_error(error, shout_output_quark(), 0,
- "neither bitrate nor quality defined");
- goto failure;
- }
-
- char *test;
- sd->bitrate = strtol(value, &test, 10);
-
- if (*test != '\0' || sd->bitrate <= 0) {
- g_set_error(error, shout_output_quark(), 0,
- "bitrate must be a positive integer");
- goto failure;
- }
- }
-
- const char *encoding = config_get_block_string(param, "encoding",
- "ogg");
- const struct encoder_plugin *encoder_plugin =
- shout_encoder_plugin_get(encoding);
- if (encoder_plugin == NULL) {
- g_set_error(error, shout_output_quark(), 0,
- "couldn't find shout encoder plugin \"%s\"",
- encoding);
- goto failure;
- }
-
- sd->encoder = encoder_init(encoder_plugin, param, error);
- if (sd->encoder == NULL)
- goto failure;
-
- unsigned shout_format;
- if (strcmp(encoding, "mp3") == 0 || strcmp(encoding, "lame") == 0)
- shout_format = SHOUT_FORMAT_MP3;
- else
- shout_format = SHOUT_FORMAT_OGG;
-
- unsigned protocol;
- value = config_get_block_string(param, "protocol", NULL);
- if (value != NULL) {
- if (0 == strcmp(value, "shoutcast") &&
- 0 != strcmp(encoding, "mp3")) {
- g_set_error(error, shout_output_quark(), 0,
- "you cannot stream \"%s\" to shoutcast, use mp3",
- encoding);
- goto failure;
- } else if (0 == strcmp(value, "shoutcast"))
- protocol = SHOUT_PROTOCOL_ICY;
- else if (0 == strcmp(value, "icecast1"))
- protocol = SHOUT_PROTOCOL_XAUDIOCAST;
- else if (0 == strcmp(value, "icecast2"))
- protocol = SHOUT_PROTOCOL_HTTP;
- else {
- g_set_error(error, shout_output_quark(), 0,
- "shout protocol \"%s\" is not \"shoutcast\" or "
- "\"icecast1\"or \"icecast2\"",
- value);
- goto failure;
- }
- } else {
- protocol = SHOUT_PROTOCOL_HTTP;
- }
-
- if (shout_set_host(sd->shout_conn, host) != SHOUTERR_SUCCESS ||
- shout_set_port(sd->shout_conn, port) != SHOUTERR_SUCCESS ||
- shout_set_password(sd->shout_conn, passwd) != SHOUTERR_SUCCESS ||
- shout_set_mount(sd->shout_conn, mount) != SHOUTERR_SUCCESS ||
- shout_set_name(sd->shout_conn, name) != SHOUTERR_SUCCESS ||
- shout_set_user(sd->shout_conn, user) != SHOUTERR_SUCCESS ||
- shout_set_public(sd->shout_conn, public) != SHOUTERR_SUCCESS ||
- shout_set_format(sd->shout_conn, shout_format)
- != SHOUTERR_SUCCESS ||
- shout_set_protocol(sd->shout_conn, protocol) != SHOUTERR_SUCCESS ||
- shout_set_agent(sd->shout_conn, "MPD") != SHOUTERR_SUCCESS) {
- g_set_error(error, shout_output_quark(), 0,
- "%s", shout_get_error(sd->shout_conn));
- goto failure;
- }
-
- /* optional paramters */
- sd->timeout = config_get_block_unsigned(param, "timeout",
- DEFAULT_CONN_TIMEOUT);
-
- value = config_get_block_string(param, "genre", NULL);
- if (value != NULL && shout_set_genre(sd->shout_conn, value)) {
- g_set_error(error, shout_output_quark(), 0,
- "%s", shout_get_error(sd->shout_conn));
- goto failure;
- }
-
- value = config_get_block_string(param, "description", NULL);
- if (value != NULL && shout_set_description(sd->shout_conn, value)) {
- g_set_error(error, shout_output_quark(), 0,
- "%s", shout_get_error(sd->shout_conn));
- goto failure;
- }
-
- value = config_get_block_string(param, "url", NULL);
- if (value != NULL && shout_set_url(sd->shout_conn, value)) {
- g_set_error(error, shout_output_quark(), 0,
- "%s", shout_get_error(sd->shout_conn));
- goto failure;
- }
-
- {
- char temp[11];
- memset(temp, 0, sizeof(temp));
-
- snprintf(temp, sizeof(temp), "%u", audio_format->channels);
- shout_set_audio_info(sd->shout_conn, SHOUT_AI_CHANNELS, temp);
-
- snprintf(temp, sizeof(temp), "%u", audio_format->sample_rate);
-
- shout_set_audio_info(sd->shout_conn, SHOUT_AI_SAMPLERATE, temp);
-
- if (sd->quality >= -1.0) {
- snprintf(temp, sizeof(temp), "%2.2f", sd->quality);
- shout_set_audio_info(sd->shout_conn, SHOUT_AI_QUALITY,
- temp);
- } else {
- snprintf(temp, sizeof(temp), "%d", sd->bitrate);
- shout_set_audio_info(sd->shout_conn, SHOUT_AI_BITRATE,
- temp);
- }
- }
-
- return &sd->base;
-
-failure:
- ao_base_finish(&sd->base);
- free_shout_data(sd);
- return NULL;
-}
-
-static bool
-handle_shout_error(struct shout_data *sd, int err, GError **error)
-{
- switch (err) {
- case SHOUTERR_SUCCESS:
- break;
-
- case SHOUTERR_UNCONNECTED:
- case SHOUTERR_SOCKET:
- g_set_error(error, shout_output_quark(), err,
- "Lost shout connection to %s:%i: %s",
- shout_get_host(sd->shout_conn),
- shout_get_port(sd->shout_conn),
- shout_get_error(sd->shout_conn));
- return false;
-
- default:
- g_set_error(error, shout_output_quark(), err,
- "connection to %s:%i error: %s",
- shout_get_host(sd->shout_conn),
- shout_get_port(sd->shout_conn),
- shout_get_error(sd->shout_conn));
- return false;
- }
-
- return true;
-}
-
-static bool
-write_page(struct shout_data *sd, GError **error)
-{
- assert(sd->encoder != NULL);
-
- while (true) {
- size_t nbytes = encoder_read(sd->encoder,
- sd->buffer, sizeof(sd->buffer));
- if (nbytes == 0)
- return true;
-
- int err = shout_send(sd->shout_conn, sd->buffer, nbytes);
- if (!handle_shout_error(sd, err, error))
- return false;
- }
-
- return true;
-}
-
-static void close_shout_conn(struct shout_data * sd)
-{
- if (sd->encoder != NULL) {
- if (encoder_end(sd->encoder, NULL))
- write_page(sd, NULL);
-
- encoder_close(sd->encoder);
- }
-
- if (shout_get_connected(sd->shout_conn) != SHOUTERR_UNCONNECTED &&
- shout_close(sd->shout_conn) != SHOUTERR_SUCCESS) {
- g_warning("problem closing connection to shout server: %s\n",
- shout_get_error(sd->shout_conn));
- }
-}
-
-static void
-my_shout_finish_driver(struct audio_output *ao)
-{
- struct shout_data *sd = (struct shout_data *)ao;
-
- encoder_finish(sd->encoder);
-
- ao_base_finish(&sd->base);
- free_shout_data(sd);
-
- shout_init_count--;
-
- if (shout_init_count == 0)
- shout_shutdown();
-}
-
-static void
-my_shout_drop_buffered_audio(struct audio_output *ao)
-{
- G_GNUC_UNUSED
- struct shout_data *sd = (struct shout_data *)ao;
-
- /* needs to be implemented for shout */
-}
-
-static void
-my_shout_close_device(struct audio_output *ao)
-{
- struct shout_data *sd = (struct shout_data *)ao;
-
- close_shout_conn(sd);
-}
-
-static bool
-shout_connect(struct shout_data *sd, GError **error)
-{
- switch (shout_open(sd->shout_conn)) {
- case SHOUTERR_SUCCESS:
- case SHOUTERR_CONNECTED:
- return true;
-
- default:
- g_set_error(error, shout_output_quark(), 0,
- "problem opening connection to shout server %s:%i: %s",
- shout_get_host(sd->shout_conn),
- shout_get_port(sd->shout_conn),
- shout_get_error(sd->shout_conn));
- return false;
- }
-}
-
-static bool
-my_shout_open_device(struct audio_output *ao, struct audio_format *audio_format,
- GError **error)
-{
- struct shout_data *sd = (struct shout_data *)ao;
-
- if (!shout_connect(sd, error))
- return false;
-
- if (!encoder_open(sd->encoder, audio_format, error)) {
- shout_close(sd->shout_conn);
- return false;
- }
-
- if (!write_page(sd, error)) {
- encoder_close(sd->encoder);
- shout_close(sd->shout_conn);
- return false;
- }
-
- return true;
-}
-
-static unsigned
-my_shout_delay(struct audio_output *ao)
-{
- struct shout_data *sd = (struct shout_data *)ao;
-
- int delay = shout_delay(sd->shout_conn);
- if (delay < 0)
- delay = 0;
-
- return delay;
-}
-
-static size_t
-my_shout_play(struct audio_output *ao, const void *chunk, size_t size,
- GError **error)
-{
- struct shout_data *sd = (struct shout_data *)ao;
-
- return encoder_write(sd->encoder, chunk, size, error) &&
- write_page(sd, error)
- ? size
- : 0;
-}
-
-static bool
-my_shout_pause(struct audio_output *ao)
-{
- static const char silence[1020];
-
- return my_shout_play(ao, silence, sizeof(silence), NULL);
-}
-
-static void
-shout_tag_to_metadata(const struct tag *tag, char *dest, size_t size)
-{
- char artist[size];
- char title[size];
-
- artist[0] = 0;
- title[0] = 0;
-
- for (unsigned i = 0; i < tag->num_items; i++) {
- switch (tag->items[i]->type) {
- case TAG_ARTIST:
- strncpy(artist, tag->items[i]->value, size);
- break;
- case TAG_TITLE:
- strncpy(title, tag->items[i]->value, size);
- break;
-
- default:
- break;
- }
- }
-
- snprintf(dest, size, "%s - %s", artist, title);
-}
-
-static void my_shout_set_tag(struct audio_output *ao,
- const struct tag *tag)
-{
- struct shout_data *sd = (struct shout_data *)ao;
- GError *error = NULL;
-
- if (sd->encoder->plugin->tag != NULL) {
- /* encoder plugin supports stream tags */
-
- if (!encoder_pre_tag(sd->encoder, &error)) {
- g_warning("%s", error->message);
- g_error_free(error);
- return;
- }
-
- if (!write_page(sd, NULL))
- return;
-
- if (!encoder_tag(sd->encoder, tag, &error)) {
- g_warning("%s", error->message);
- g_error_free(error);
- }
- } else {
- /* no stream tag support: fall back to icy-metadata */
- char song[1024];
- shout_tag_to_metadata(tag, song, sizeof(song));
-
- shout_metadata_add(sd->shout_meta, "song", song);
- if (SHOUTERR_SUCCESS != shout_set_metadata(sd->shout_conn,
- sd->shout_meta)) {
- g_warning("error setting shout metadata\n");
- }
- }
-
- write_page(sd, NULL);
-}
-
-const struct audio_output_plugin shout_output_plugin = {
- .name = "shout",
- .init = my_shout_init_driver,
- .finish = my_shout_finish_driver,
- .open = my_shout_open_device,
- .delay = my_shout_delay,
- .play = my_shout_play,
- .pause = my_shout_pause,
- .cancel = my_shout_drop_buffered_audio,
- .close = my_shout_close_device,
- .send_tag = my_shout_set_tag,
-};
diff --git a/src/output/shout_output_plugin.h b/src/output/shout_output_plugin.h
deleted file mode 100644
index 9a7378803..000000000
--- a/src/output/shout_output_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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_SHOUT_OUTPUT_PLUGIN_H
-#define MPD_SHOUT_OUTPUT_PLUGIN_H
-
-extern const struct audio_output_plugin shout_output_plugin;
-
-#endif
diff --git a/src/output/solaris_output_plugin.c b/src/output/solaris_output_plugin.c
deleted file mode 100644
index ce726009a..000000000
--- a/src/output/solaris_output_plugin.c
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "solaris_output_plugin.h"
-#include "output_api.h"
-#include "fd_util.h"
-
-#include <glib.h>
-
-#include <sys/stropts.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <errno.h>
-
-#ifdef __sun
-#include <sys/audio.h>
-#else
-
-/* some fake declarations that allow build this plugin on systems
- other than Solaris, just to see if it compiles */
-
-#define AUDIO_GETINFO 0
-#define AUDIO_SETINFO 0
-#define AUDIO_ENCODING_LINEAR 0
-
-struct audio_info {
- struct {
- unsigned sample_rate, channels, precision, encoding;
- } play;
-};
-
-#endif
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "solaris_output"
-
-struct solaris_output {
- struct audio_output base;
-
- /* configuration */
- const char *device;
-
- int fd;
-};
-
-/**
- * The quark used for GError.domain.
- */
-static inline GQuark
-solaris_output_quark(void)
-{
- return g_quark_from_static_string("solaris_output");
-}
-
-static bool
-solaris_output_test_default_device(void)
-{
- struct stat st;
-
- return stat("/dev/audio", &st) == 0 && S_ISCHR(st.st_mode) &&
- access("/dev/audio", W_OK) == 0;
-}
-
-static struct audio_output *
-solaris_output_init(const struct config_param *param, GError **error_r)
-{
- struct solaris_output *so = g_new(struct solaris_output, 1);
-
- if (!ao_base_init(&so->base, &solaris_output_plugin, param, error_r)) {
- g_free(so);
- return NULL;
- }
-
- so->device = config_get_block_string(param, "device", "/dev/audio");
-
- return &so->base;
-}
-
-static void
-solaris_output_finish(struct audio_output *ao)
-{
- struct solaris_output *so = (struct solaris_output *)ao;
-
- ao_base_finish(&so->base);
- g_free(so);
-}
-
-static bool
-solaris_output_open(struct audio_output *ao, struct audio_format *audio_format,
- GError **error)
-{
- struct solaris_output *so = (struct solaris_output *)ao;
- struct audio_info info;
- int ret, flags;
-
- /* support only 16 bit mono/stereo for now; nothing else has
- been tested */
- audio_format->format = SAMPLE_FORMAT_S16;
-
- /* open the device in non-blocking mode */
-
- so->fd = open_cloexec(so->device, O_WRONLY|O_NONBLOCK, 0);
- if (so->fd < 0) {
- g_set_error(error, solaris_output_quark(), errno,
- "Failed to open %s: %s",
- so->device, g_strerror(errno));
- return false;
- }
-
- /* restore blocking mode */
-
- flags = fcntl(so->fd, F_GETFL);
- if (flags > 0 && (flags & O_NONBLOCK) != 0)
- fcntl(so->fd, F_SETFL, flags & ~O_NONBLOCK);
-
- /* configure the audio device */
-
- ret = ioctl(so->fd, AUDIO_GETINFO, &info);
- if (ret < 0) {
- g_set_error(error, solaris_output_quark(), errno,
- "AUDIO_GETINFO failed: %s", g_strerror(errno));
- close(so->fd);
- return false;
- }
-
- info.play.sample_rate = audio_format->sample_rate;
- info.play.channels = audio_format->channels;
- info.play.precision = 16;
- info.play.encoding = AUDIO_ENCODING_LINEAR;
-
- ret = ioctl(so->fd, AUDIO_SETINFO, &info);
- if (ret < 0) {
- g_set_error(error, solaris_output_quark(), errno,
- "AUDIO_SETINFO failed: %s", g_strerror(errno));
- close(so->fd);
- return false;
- }
-
- return true;
-}
-
-static void
-solaris_output_close(struct audio_output *ao)
-{
- struct solaris_output *so = (struct solaris_output *)ao;
-
- close(so->fd);
-}
-
-static size_t
-solaris_output_play(struct audio_output *ao, const void *chunk, size_t size,
- GError **error)
-{
- struct solaris_output *so = (struct solaris_output *)ao;
- ssize_t nbytes;
-
- nbytes = write(so->fd, chunk, size);
- if (nbytes <= 0) {
- g_set_error(error, solaris_output_quark(), errno,
- "Write failed: %s", g_strerror(errno));
- return 0;
- }
-
- return nbytes;
-}
-
-static void
-solaris_output_cancel(struct audio_output *ao)
-{
- struct solaris_output *so = (struct solaris_output *)ao;
-
- ioctl(so->fd, I_FLUSH);
-}
-
-const struct audio_output_plugin solaris_output_plugin = {
- .name = "solaris",
- .test_default_device = solaris_output_test_default_device,
- .init = solaris_output_init,
- .finish = solaris_output_finish,
- .open = solaris_output_open,
- .close = solaris_output_close,
- .play = solaris_output_play,
- .cancel = solaris_output_cancel,
-};
diff --git a/src/output/solaris_output_plugin.h b/src/output/solaris_output_plugin.h
deleted file mode 100644
index 600aea8c2..000000000
--- a/src/output/solaris_output_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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_SOLARIS_OUTPUT_PLUGIN_H
-#define MPD_SOLARIS_OUTPUT_PLUGIN_H
-
-extern const struct audio_output_plugin solaris_output_plugin;
-
-#endif
diff --git a/src/output/winmm_output_plugin.c b/src/output/winmm_output_plugin.c
deleted file mode 100644
index 4d95834b9..000000000
--- a/src/output/winmm_output_plugin.c
+++ /dev/null
@@ -1,356 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "winmm_output_plugin.h"
-#include "output_api.h"
-#include "pcm_buffer.h"
-#include "mixer_list.h"
-#include "winmm_output_plugin.h"
-
-#include <stdlib.h>
-#include <string.h>
-#include <windows.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "winmm_output"
-
-struct winmm_buffer {
- struct pcm_buffer buffer;
-
- WAVEHDR hdr;
-};
-
-struct winmm_output {
- struct audio_output base;
-
- UINT device_id;
- HWAVEOUT handle;
-
- /**
- * This event is triggered by Windows when a buffer is
- * finished.
- */
- HANDLE event;
-
- struct winmm_buffer buffers[8];
- unsigned next_buffer;
-};
-
-/**
- * The quark used for GError.domain.
- */
-static inline GQuark
-winmm_output_quark(void)
-{
- return g_quark_from_static_string("winmm_output");
-}
-
-HWAVEOUT
-winmm_output_get_handle(struct winmm_output* output)
-{
- return output->handle;
-}
-
-static bool
-winmm_output_test_default_device(void)
-{
- return waveOutGetNumDevs() > 0;
-}
-
-static bool
-get_device_id(const char *device_name, UINT *device_id, GError **error_r)
-{
- /* if device is not specified use wave mapper */
- if (device_name == NULL) {
- *device_id = WAVE_MAPPER;
- return true;
- }
-
- UINT numdevs = waveOutGetNumDevs();
-
- /* check for device id */
- char *endptr;
- UINT id = strtoul(device_name, &endptr, 0);
- if (endptr > device_name && *endptr == 0) {
- if (id >= numdevs)
- goto fail;
- *device_id = id;
- return true;
- }
-
- /* check for device name */
- for (UINT i = 0; i < numdevs; i++) {
- WAVEOUTCAPS caps;
- MMRESULT result = waveOutGetDevCaps(i, &caps, sizeof(caps));
- if (result != MMSYSERR_NOERROR)
- continue;
- /* szPname is only 32 chars long, so it is often truncated.
- Use partial match to work around this. */
- if (strstr(device_name, caps.szPname) == device_name) {
- *device_id = i;
- return true;
- }
- }
-
-fail:
- g_set_error(error_r, winmm_output_quark(), 0,
- "device \"%s\" is not found", device_name);
- return false;
-}
-
-static struct audio_output *
-winmm_output_init(const struct config_param *param, GError **error_r)
-{
- struct winmm_output *wo = g_new(struct winmm_output, 1);
- if (!ao_base_init(&wo->base, &winmm_output_plugin, param, error_r)) {
- g_free(wo);
- return NULL;
- }
-
- const char *device = config_get_block_string(param, "device", NULL);
- if (!get_device_id(device, &wo->device_id, error_r)) {
- ao_base_finish(&wo->base);
- g_free(wo);
- return NULL;
- }
-
- return &wo->base;
-}
-
-static void
-winmm_output_finish(struct audio_output *ao)
-{
- struct winmm_output *wo = (struct winmm_output *)ao;
-
- ao_base_finish(&wo->base);
- g_free(wo);
-}
-
-static bool
-winmm_output_open(struct audio_output *ao, struct audio_format *audio_format,
- GError **error_r)
-{
- struct winmm_output *wo = (struct winmm_output *)ao;
-
- wo->event = CreateEvent(NULL, false, false, NULL);
- if (wo->event == NULL) {
- g_set_error(error_r, winmm_output_quark(), 0,
- "CreateEvent() failed");
- return false;
- }
-
- switch (audio_format->format) {
- case SAMPLE_FORMAT_S8:
- case SAMPLE_FORMAT_S16:
- break;
-
- case SAMPLE_FORMAT_S24_P32:
- case SAMPLE_FORMAT_S32:
- case SAMPLE_FORMAT_UNDEFINED:
- /* we havn't tested formats other than S16 */
- audio_format->format = SAMPLE_FORMAT_S16;
- break;
- }
-
- if (audio_format->channels > 2)
- /* same here: more than stereo was not tested */
- audio_format->channels = 2;
-
- WAVEFORMATEX format;
- format.wFormatTag = WAVE_FORMAT_PCM;
- format.nChannels = audio_format->channels;
- format.nSamplesPerSec = audio_format->sample_rate;
- format.nBlockAlign = audio_format_frame_size(audio_format);
- format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
- format.wBitsPerSample = audio_format_sample_size(audio_format) * 8;
- format.cbSize = 0;
-
- MMRESULT result = waveOutOpen(&wo->handle, wo->device_id, &format,
- (DWORD_PTR)wo->event, 0, CALLBACK_EVENT);
- if (result != MMSYSERR_NOERROR) {
- CloseHandle(wo->event);
- g_set_error(error_r, winmm_output_quark(), result,
- "waveOutOpen() failed");
- return false;
- }
-
- for (unsigned i = 0; i < G_N_ELEMENTS(wo->buffers); ++i) {
- pcm_buffer_init(&wo->buffers[i].buffer);
- memset(&wo->buffers[i].hdr, 0, sizeof(wo->buffers[i].hdr));
- }
-
- wo->next_buffer = 0;
-
- return true;
-}
-
-static void
-winmm_output_close(struct audio_output *ao)
-{
- struct winmm_output *wo = (struct winmm_output *)ao;
-
- for (unsigned i = 0; i < G_N_ELEMENTS(wo->buffers); ++i)
- pcm_buffer_deinit(&wo->buffers[i].buffer);
-
- waveOutClose(wo->handle);
-
- CloseHandle(wo->event);
-}
-
-/**
- * Copy data into a buffer, and prepare the wave header.
- */
-static bool
-winmm_set_buffer(struct winmm_output *wo, struct winmm_buffer *buffer,
- const void *data, size_t size,
- GError **error_r)
-{
- void *dest = pcm_buffer_get(&buffer->buffer, size);
- assert(dest != NULL);
-
- memcpy(dest, data, size);
-
- memset(&buffer->hdr, 0, sizeof(buffer->hdr));
- buffer->hdr.lpData = dest;
- buffer->hdr.dwBufferLength = size;
-
- MMRESULT result = waveOutPrepareHeader(wo->handle, &buffer->hdr,
- sizeof(buffer->hdr));
- if (result != MMSYSERR_NOERROR) {
- g_set_error(error_r, winmm_output_quark(), result,
- "waveOutPrepareHeader() failed");
- return false;
- }
-
- return true;
-}
-
-/**
- * Wait until the buffer is finished.
- */
-static bool
-winmm_drain_buffer(struct winmm_output *wo, struct winmm_buffer *buffer,
- GError **error_r)
-{
- if ((buffer->hdr.dwFlags & WHDR_DONE) == WHDR_DONE)
- /* already finished */
- return true;
-
- while (true) {
- MMRESULT result = waveOutUnprepareHeader(wo->handle,
- &buffer->hdr,
- sizeof(buffer->hdr));
- if (result == MMSYSERR_NOERROR)
- return true;
- else if (result != WAVERR_STILLPLAYING) {
- g_set_error(error_r, winmm_output_quark(), result,
- "waveOutUnprepareHeader() failed");
- return false;
- }
-
- /* wait some more */
- WaitForSingleObject(wo->event, INFINITE);
- }
-}
-
-static size_t
-winmm_output_play(struct audio_output *ao, const void *chunk, size_t size, GError **error_r)
-{
- struct winmm_output *wo = (struct winmm_output *)ao;
-
- /* get the next buffer from the ring and prepare it */
- struct winmm_buffer *buffer = &wo->buffers[wo->next_buffer];
- if (!winmm_drain_buffer(wo, buffer, error_r) ||
- !winmm_set_buffer(wo, buffer, chunk, size, error_r))
- return 0;
-
- /* enqueue the buffer */
- MMRESULT result = waveOutWrite(wo->handle, &buffer->hdr,
- sizeof(buffer->hdr));
- if (result != MMSYSERR_NOERROR) {
- waveOutUnprepareHeader(wo->handle, &buffer->hdr,
- sizeof(buffer->hdr));
- g_set_error(error_r, winmm_output_quark(), result,
- "waveOutWrite() failed");
- return 0;
- }
-
- /* mark our buffer as "used" */
- wo->next_buffer = (wo->next_buffer + 1) %
- G_N_ELEMENTS(wo->buffers);
-
- return size;
-}
-
-static bool
-winmm_drain_all_buffers(struct winmm_output *wo, GError **error_r)
-{
- for (unsigned i = wo->next_buffer; i < G_N_ELEMENTS(wo->buffers); ++i)
- if (!winmm_drain_buffer(wo, &wo->buffers[i], error_r))
- return false;
-
- for (unsigned i = 0; i < wo->next_buffer; ++i)
- if (!winmm_drain_buffer(wo, &wo->buffers[i], error_r))
- return false;
-
- return true;
-}
-
-static void
-winmm_stop(struct winmm_output *wo)
-{
- waveOutReset(wo->handle);
-
- for (unsigned i = 0; i < G_N_ELEMENTS(wo->buffers); ++i) {
- struct winmm_buffer *buffer = &wo->buffers[i];
- waveOutUnprepareHeader(wo->handle, &buffer->hdr,
- sizeof(buffer->hdr));
- }
-}
-
-static void
-winmm_output_drain(struct audio_output *ao)
-{
- struct winmm_output *wo = (struct winmm_output *)ao;
-
- if (!winmm_drain_all_buffers(wo, NULL))
- winmm_stop(wo);
-}
-
-static void
-winmm_output_cancel(struct audio_output *ao)
-{
- struct winmm_output *wo = (struct winmm_output *)ao;
-
- winmm_stop(wo);
-}
-
-const struct audio_output_plugin winmm_output_plugin = {
- .name = "winmm",
- .test_default_device = winmm_output_test_default_device,
- .init = winmm_output_init,
- .finish = winmm_output_finish,
- .open = winmm_output_open,
- .close = winmm_output_close,
- .play = winmm_output_play,
- .drain = winmm_output_drain,
- .cancel = winmm_output_cancel,
- .mixer_plugin = &winmm_mixer_plugin,
-};
diff --git a/src/output/winmm_output_plugin.h b/src/output/winmm_output_plugin.h
deleted file mode 100644
index 0605530e1..000000000
--- a/src/output/winmm_output_plugin.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * 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_WINMM_OUTPUT_PLUGIN_H
-#define MPD_WINMM_OUTPUT_PLUGIN_H
-
-#include "check.h"
-
-#ifdef ENABLE_WINMM_OUTPUT
-
-#include <windows.h>
-
-struct winmm_output;
-
-extern const struct audio_output_plugin winmm_output_plugin;
-
-HWAVEOUT winmm_output_get_handle(struct winmm_output*);
-
-#endif
-
-#endif
diff --git a/src/output_all.c b/src/output_all.c
deleted file mode 100644
index f56cd04ee..000000000
--- a/src/output_all.c
+++ /dev/null
@@ -1,590 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "output_all.h"
-#include "output_internal.h"
-#include "output_control.h"
-#include "chunk.h"
-#include "conf.h"
-#include "pipe.h"
-#include "buffer.h"
-#include "player_control.h"
-#include "mpd_error.h"
-#include "notify.h"
-
-#ifndef NDEBUG
-#include "chunk.h"
-#endif
-
-#include <assert.h>
-#include <string.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "output"
-
-static struct audio_format input_audio_format;
-
-static struct audio_output **audio_outputs;
-static unsigned int num_audio_outputs;
-
-/**
- * The #music_buffer object where consumed chunks are returned.
- */
-static struct music_buffer *g_music_buffer;
-
-/**
- * The #music_pipe object which feeds all audio outputs. It is filled
- * by audio_output_all_play().
- */
-static struct music_pipe *g_mp;
-
-/**
- * The "elapsed_time" stamp of the most recently finished chunk.
- */
-static float audio_output_all_elapsed_time = -1.0;
-
-unsigned int audio_output_count(void)
-{
- return num_audio_outputs;
-}
-
-struct audio_output *
-audio_output_get(unsigned i)
-{
- assert(i < num_audio_outputs);
-
- assert(audio_outputs[i] != NULL);
-
- return audio_outputs[i];
-}
-
-struct audio_output *
-audio_output_find(const char *name)
-{
- for (unsigned i = 0; i < num_audio_outputs; ++i) {
- struct audio_output *ao = audio_output_get(i);
-
- if (strcmp(ao->name, name) == 0)
- return ao;
- }
-
- /* name not found */
- return NULL;
-}
-
-static unsigned
-audio_output_config_count(void)
-{
- unsigned int nr = 0;
- const struct config_param *param = NULL;
-
- while ((param = config_get_next_param(CONF_AUDIO_OUTPUT, param)))
- nr++;
- if (!nr)
- nr = 1; /* we'll always have at least one device */
- return nr;
-}
-
-void
-audio_output_all_init(struct player_control *pc)
-{
- const struct config_param *param = NULL;
- unsigned int i;
- GError *error = NULL;
-
- notify_init(&audio_output_client_notify);
-
- num_audio_outputs = audio_output_config_count();
- audio_outputs = g_new(struct audio_output *, num_audio_outputs);
-
- for (i = 0; i < num_audio_outputs; i++)
- {
- unsigned int j;
-
- param = config_get_next_param(CONF_AUDIO_OUTPUT, param);
-
- /* only allow param to be NULL if there just one audioOutput */
- assert(param || (num_audio_outputs == 1));
-
- struct audio_output *output = audio_output_new(param, pc, &error);
- if (output == NULL) {
- if (param != NULL)
- MPD_ERROR("line %i: %s",
- param->line, error->message);
- else
- MPD_ERROR("%s", error->message);
- }
-
- audio_outputs[i] = output;
-
- /* require output names to be unique: */
- for (j = 0; j < i; j++) {
- if (!strcmp(output->name, audio_outputs[j]->name)) {
- MPD_ERROR("output devices with identical "
- "names: %s\n", output->name);
- }
- }
- }
-}
-
-void
-audio_output_all_finish(void)
-{
- unsigned int i;
-
- for (i = 0; i < num_audio_outputs; i++) {
- audio_output_disable(audio_outputs[i]);
- audio_output_finish(audio_outputs[i]);
- }
-
- g_free(audio_outputs);
- audio_outputs = NULL;
- num_audio_outputs = 0;
-
- notify_deinit(&audio_output_client_notify);
-}
-
-void
-audio_output_all_enable_disable(void)
-{
- for (unsigned i = 0; i < num_audio_outputs; i++) {
- struct audio_output *ao = audio_outputs[i];
- bool enabled;
-
- g_mutex_lock(ao->mutex);
- enabled = ao->really_enabled;
- g_mutex_unlock(ao->mutex);
-
- if (ao->enabled != enabled) {
- if (ao->enabled)
- audio_output_enable(ao);
- else
- audio_output_disable(ao);
- }
- }
-}
-
-/**
- * Determine if all (active) outputs have finished the current
- * command.
- */
-static bool
-audio_output_all_finished(void)
-{
- for (unsigned i = 0; i < num_audio_outputs; ++i) {
- struct audio_output *ao = audio_outputs[i];
- bool not_finished;
-
- g_mutex_lock(ao->mutex);
- not_finished = audio_output_is_open(ao) &&
- !audio_output_command_is_finished(ao);
- g_mutex_unlock(ao->mutex);
-
- if (not_finished)
- return false;
- }
-
- return true;
-}
-
-static void audio_output_wait_all(void)
-{
- while (!audio_output_all_finished())
- notify_wait(&audio_output_client_notify);
-}
-
-/**
- * Signals all audio outputs which are open.
- */
-static void
-audio_output_allow_play_all(void)
-{
- for (unsigned i = 0; i < num_audio_outputs; ++i)
- audio_output_allow_play(audio_outputs[i]);
-}
-
-static void
-audio_output_reset_reopen(struct audio_output *ao)
-{
- g_mutex_lock(ao->mutex);
-
- if (!ao->open && ao->fail_timer != NULL) {
- g_timer_destroy(ao->fail_timer);
- ao->fail_timer = NULL;
- }
-
- g_mutex_unlock(ao->mutex);
-}
-
-/**
- * Resets the "reopen" flag on all audio devices. MPD should
- * immediately retry to open the device instead of waiting for the
- * timeout when the user wants to start playback.
- */
-static void
-audio_output_all_reset_reopen(void)
-{
- for (unsigned i = 0; i < num_audio_outputs; ++i) {
- struct audio_output *ao = audio_outputs[i];
-
- audio_output_reset_reopen(ao);
- }
-}
-
-/**
- * Opens all output devices which are enabled, but closed.
- *
- * @return true if there is at least open output device which is open
- */
-static bool
-audio_output_all_update(void)
-{
- unsigned int i;
- bool ret = false;
-
- if (!audio_format_defined(&input_audio_format))
- return false;
-
- for (i = 0; i < num_audio_outputs; ++i)
- ret = audio_output_update(audio_outputs[i],
- &input_audio_format, g_mp) || ret;
-
- return ret;
-}
-
-bool
-audio_output_all_play(struct music_chunk *chunk)
-{
- bool ret;
- unsigned int i;
-
- assert(g_music_buffer != NULL);
- assert(g_mp != NULL);
- assert(chunk != NULL);
- assert(music_chunk_check_format(chunk, &input_audio_format));
-
- ret = audio_output_all_update();
- if (!ret)
- return false;
-
- music_pipe_push(g_mp, chunk);
-
- for (i = 0; i < num_audio_outputs; ++i)
- audio_output_play(audio_outputs[i]);
-
- return true;
-}
-
-bool
-audio_output_all_open(const struct audio_format *audio_format,
- struct music_buffer *buffer)
-{
- bool ret = false, enabled = false;
- unsigned int i;
-
- assert(audio_format != NULL);
- assert(buffer != NULL);
- assert(g_music_buffer == NULL || g_music_buffer == buffer);
- assert((g_mp == NULL) == (g_music_buffer == NULL));
-
- g_music_buffer = buffer;
-
- /* the audio format must be the same as existing chunks in the
- pipe */
- assert(g_mp == NULL || music_pipe_check_format(g_mp, audio_format));
-
- if (g_mp == NULL)
- g_mp = music_pipe_new();
- else
- /* if the pipe hasn't been cleared, the the audio
- format must not have changed */
- assert(music_pipe_empty(g_mp) ||
- audio_format_equals(audio_format,
- &input_audio_format));
-
- input_audio_format = *audio_format;
-
- audio_output_all_reset_reopen();
- audio_output_all_enable_disable();
- audio_output_all_update();
-
- for (i = 0; i < num_audio_outputs; ++i) {
- if (audio_outputs[i]->enabled)
- enabled = true;
-
- if (audio_outputs[i]->open)
- ret = true;
- }
-
- if (!enabled)
- g_warning("All audio outputs are disabled");
-
- if (!ret)
- /* close all devices if there was an error */
- audio_output_all_close();
-
- return ret;
-}
-
-/**
- * Has the specified audio output already consumed this chunk?
- */
-static bool
-chunk_is_consumed_in(const struct audio_output *ao,
- const struct music_chunk *chunk)
-{
- if (!ao->open)
- return true;
-
- if (ao->chunk == NULL)
- return false;
-
- assert(chunk == ao->chunk || music_pipe_contains(g_mp, ao->chunk));
-
- if (chunk != ao->chunk) {
- assert(chunk->next != NULL);
- return true;
- }
-
- return ao->chunk_finished && chunk->next == NULL;
-}
-
-/**
- * Has this chunk been consumed by all audio outputs?
- */
-static bool
-chunk_is_consumed(const struct music_chunk *chunk)
-{
- for (unsigned i = 0; i < num_audio_outputs; ++i) {
- const struct audio_output *ao = audio_outputs[i];
- bool consumed;
-
- g_mutex_lock(ao->mutex);
- consumed = chunk_is_consumed_in(ao, chunk);
- g_mutex_unlock(ao->mutex);
-
- if (!consumed)
- return false;
- }
-
- return true;
-}
-
-/**
- * There's only one chunk left in the pipe (#g_mp), and all audio
- * outputs have consumed it already. Clear the reference.
- */
-static void
-clear_tail_chunk(G_GNUC_UNUSED const struct music_chunk *chunk, bool *locked)
-{
- assert(chunk->next == NULL);
- assert(music_pipe_contains(g_mp, chunk));
-
- for (unsigned i = 0; i < num_audio_outputs; ++i) {
- struct audio_output *ao = audio_outputs[i];
-
- /* this mutex will be unlocked by the caller when it's
- ready */
- g_mutex_lock(ao->mutex);
- locked[i] = ao->open;
-
- if (!locked[i]) {
- g_mutex_unlock(ao->mutex);
- continue;
- }
-
- assert(ao->chunk == chunk);
- assert(ao->chunk_finished);
- ao->chunk = NULL;
- }
-}
-
-unsigned
-audio_output_all_check(void)
-{
- const struct music_chunk *chunk;
- bool is_tail;
- struct music_chunk *shifted;
- bool locked[num_audio_outputs];
-
- assert(g_music_buffer != NULL);
- assert(g_mp != NULL);
-
- while ((chunk = music_pipe_peek(g_mp)) != NULL) {
- assert(!music_pipe_empty(g_mp));
-
- if (!chunk_is_consumed(chunk))
- /* at least one output is not finished playing
- this chunk */
- return music_pipe_size(g_mp);
-
- if (chunk->length > 0 && chunk->times >= 0.0)
- /* only update elapsed_time if the chunk
- provides a defined value */
- audio_output_all_elapsed_time = chunk->times;
-
- is_tail = chunk->next == NULL;
- if (is_tail)
- /* this is the tail of the pipe - clear the
- chunk reference in all outputs */
- clear_tail_chunk(chunk, locked);
-
- /* remove the chunk from the pipe */
- shifted = music_pipe_shift(g_mp);
- assert(shifted == chunk);
-
- if (is_tail)
- /* unlock all audio outputs which were locked
- by clear_tail_chunk() */
- for (unsigned i = 0; i < num_audio_outputs; ++i)
- if (locked[i])
- g_mutex_unlock(audio_outputs[i]->mutex);
-
- /* return the chunk to the buffer */
- music_buffer_return(g_music_buffer, shifted);
- }
-
- return 0;
-}
-
-bool
-audio_output_all_wait(struct player_control *pc, unsigned threshold)
-{
- player_lock(pc);
-
- if (audio_output_all_check() < threshold) {
- player_unlock(pc);
- return true;
- }
-
- player_wait(pc);
- player_unlock(pc);
-
- return audio_output_all_check() < threshold;
-}
-
-void
-audio_output_all_pause(void)
-{
- unsigned int i;
-
- audio_output_all_update();
-
- for (i = 0; i < num_audio_outputs; ++i)
- audio_output_pause(audio_outputs[i]);
-
- audio_output_wait_all();
-}
-
-void
-audio_output_all_drain(void)
-{
- for (unsigned i = 0; i < num_audio_outputs; ++i)
- audio_output_drain_async(audio_outputs[i]);
-
- audio_output_wait_all();
-}
-
-void
-audio_output_all_cancel(void)
-{
- unsigned int i;
-
- /* send the cancel() command to all audio outputs */
-
- for (i = 0; i < num_audio_outputs; ++i)
- audio_output_cancel(audio_outputs[i]);
-
- audio_output_wait_all();
-
- /* clear the music pipe and return all chunks to the buffer */
-
- if (g_mp != NULL)
- music_pipe_clear(g_mp, g_music_buffer);
-
- /* the audio outputs are now waiting for a signal, to
- synchronize the cleared music pipe */
-
- audio_output_allow_play_all();
-
- /* invalidate elapsed_time */
-
- audio_output_all_elapsed_time = -1.0;
-}
-
-void
-audio_output_all_close(void)
-{
- unsigned int i;
-
- for (i = 0; i < num_audio_outputs; ++i)
- audio_output_close(audio_outputs[i]);
-
- if (g_mp != NULL) {
- assert(g_music_buffer != NULL);
-
- music_pipe_clear(g_mp, g_music_buffer);
- music_pipe_free(g_mp);
- g_mp = NULL;
- }
-
- g_music_buffer = NULL;
-
- audio_format_clear(&input_audio_format);
-
- audio_output_all_elapsed_time = -1.0;
-}
-
-void
-audio_output_all_release(void)
-{
- unsigned int i;
-
- for (i = 0; i < num_audio_outputs; ++i)
- audio_output_release(audio_outputs[i]);
-
- if (g_mp != NULL) {
- assert(g_music_buffer != NULL);
-
- music_pipe_clear(g_mp, g_music_buffer);
- music_pipe_free(g_mp);
- g_mp = NULL;
- }
-
- g_music_buffer = NULL;
-
- audio_format_clear(&input_audio_format);
-
- audio_output_all_elapsed_time = -1.0;
-}
-
-void
-audio_output_all_song_border(void)
-{
- /* clear the elapsed_time pointer at the beginning of a new
- song */
- audio_output_all_elapsed_time = 0.0;
-}
-
-float
-audio_output_all_get_elapsed_time(void)
-{
- return audio_output_all_elapsed_time;
-}
diff --git a/src/output_all.h b/src/output_all.h
deleted file mode 100644
index 4eeb94f13..000000000
--- a/src/output_all.h
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * Functions for dealing with all configured (enabled) audion outputs
- * at once.
- *
- */
-
-#ifndef OUTPUT_ALL_H
-#define OUTPUT_ALL_H
-
-#include <stdbool.h>
-#include <stddef.h>
-
-struct audio_format;
-struct music_buffer;
-struct music_chunk;
-struct player_control;
-
-/**
- * Global initialization: load audio outputs from the configuration
- * file and initialize them.
- */
-void
-audio_output_all_init(struct player_control *pc);
-
-/**
- * Global finalization: free memory occupied by audio outputs. All
- */
-void
-audio_output_all_finish(void);
-
-/**
- * Returns the total number of audio output devices, including those
- * who are disabled right now.
- */
-unsigned int audio_output_count(void);
-
-/**
- * Returns the "i"th audio output device.
- */
-struct audio_output *
-audio_output_get(unsigned i);
-
-/**
- * Returns the audio output device with the specified name. Returns
- * NULL if the name does not exist.
- */
-struct audio_output *
-audio_output_find(const char *name);
-
-/**
- * Checks the "enabled" flag of all audio outputs, and if one has
- * changed, commit the change.
- */
-void
-audio_output_all_enable_disable(void);
-
-/**
- * Opens all audio outputs which are not disabled.
- *
- * @param audio_format the preferred audio format, or NULL to reuse
- * the previous format
- * @param buffer the #music_buffer where consumed #music_chunk objects
- * should be returned
- * @return true on success, false on failure
- */
-bool
-audio_output_all_open(const struct audio_format *audio_format,
- struct music_buffer *buffer);
-
-/**
- * Closes all audio outputs.
- */
-void
-audio_output_all_close(void);
-
-/**
- * Closes all audio outputs. Outputs with the "always_on" flag are
- * put into pause mode.
- */
-void
-audio_output_all_release(void);
-
-/**
- * Enqueue a #music_chunk object for playing, i.e. pushes it to a
- * #music_pipe.
- *
- * @param chunk the #music_chunk object to be played
- * @return true on success, false if no audio output was able to play
- * (all closed then)
- */
-bool
-audio_output_all_play(struct music_chunk *chunk);
-
-/**
- * Checks if the output devices have drained their music pipe, and
- * returns the consumed music chunks to the #music_buffer.
- *
- * @return the number of chunks to play left in the #music_pipe
- */
-unsigned
-audio_output_all_check(void);
-
-/**
- * Checks if the size of the #music_pipe is below the #threshold. If
- * not, it attempts to synchronize with all output threads, and waits
- * until another #music_chunk is finished.
- *
- * @param threshold the maximum number of chunks in the pipe
- * @return true if there are less than #threshold chunks in the pipe
- */
-bool
-audio_output_all_wait(struct player_control *pc, unsigned threshold);
-
-/**
- * Puts all audio outputs into pause mode. Most implementations will
- * simply close it then.
- */
-void
-audio_output_all_pause(void);
-
-/**
- * Drain all audio outputs.
- */
-void
-audio_output_all_drain(void);
-
-/**
- * Try to cancel data which may still be in the device's buffers.
- */
-void
-audio_output_all_cancel(void);
-
-/**
- * Indicate that a new song will begin now.
- */
-void
-audio_output_all_song_border(void);
-
-/**
- * Returns the "elapsed_time" stamp of the most recently finished
- * chunk. A negative value is returned when no chunk has been
- * finished yet.
- */
-float
-audio_output_all_get_elapsed_time(void);
-
-#endif
diff --git a/src/output_api.h b/src/output_api.h
deleted file mode 100644
index dfeef3518..000000000
--- a/src/output_api.h
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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_OUTPUT_API_H
-#define MPD_OUTPUT_API_H
-
-#include "output_plugin.h"
-#include "output_internal.h"
-#include "audio_format.h"
-#include "tag.h"
-#include "conf.h"
-
-#endif
diff --git a/src/output_command.c b/src/output_command.c
deleted file mode 100644
index 3988f350a..000000000
--- a/src/output_command.c
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * Glue functions for controlling the audio outputs over the MPD
- * protocol. These functions perform extra validation on all
- * parameters, because they might be from an untrusted source.
- *
- */
-
-#include "config.h"
-#include "output_command.h"
-#include "output_all.h"
-#include "output_internal.h"
-#include "output_plugin.h"
-#include "mixer_control.h"
-#include "player_control.h"
-#include "idle.h"
-
-extern unsigned audio_output_state_version;
-
-bool
-audio_output_enable_index(unsigned idx)
-{
- struct audio_output *ao;
-
- if (idx >= audio_output_count())
- return false;
-
- ao = audio_output_get(idx);
- if (ao->enabled)
- return true;
-
- ao->enabled = true;
- idle_add(IDLE_OUTPUT);
-
- pc_update_audio(ao->player_control);
-
- ++audio_output_state_version;
-
- return true;
-}
-
-bool
-audio_output_disable_index(unsigned idx)
-{
- struct audio_output *ao;
- struct mixer *mixer;
-
- if (idx >= audio_output_count())
- return false;
-
- ao = audio_output_get(idx);
- if (!ao->enabled)
- return true;
-
- ao->enabled = false;
- idle_add(IDLE_OUTPUT);
-
- mixer = ao->mixer;
- if (mixer != NULL) {
- mixer_close(mixer);
- idle_add(IDLE_MIXER);
- }
-
- pc_update_audio(ao->player_control);
-
- ++audio_output_state_version;
-
- return true;
-}
diff --git a/src/output_command.h b/src/output_command.h
deleted file mode 100644
index eda30acc8..000000000
--- a/src/output_command.h
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * Glue functions for controlling the audio outputs over the MPD
- * protocol. These functions perform extra validation on all
- * parameters, because they might be from an untrusted source.
- *
- */
-
-#ifndef OUTPUT_COMMAND_H
-#define OUTPUT_COMMAND_H
-
-#include <stdbool.h>
-
-/**
- * Enables an audio output. Returns false if the specified output
- * does not exist.
- */
-bool
-audio_output_enable_index(unsigned idx);
-
-/**
- * Disables an audio output. Returns false if the specified output
- * does not exist.
- */
-bool
-audio_output_disable_index(unsigned idx);
-
-#endif
diff --git a/src/output_control.c b/src/output_control.c
deleted file mode 100644
index 7b95be49b..000000000
--- a/src/output_control.c
+++ /dev/null
@@ -1,336 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "output_control.h"
-#include "output_api.h"
-#include "output_internal.h"
-#include "output_thread.h"
-#include "mixer_control.h"
-#include "mixer_plugin.h"
-#include "filter_plugin.h"
-#include "notify.h"
-
-#include <assert.h>
-#include <stdlib.h>
-
-enum {
- /** after a failure, wait this number of seconds before
- automatically reopening the device */
- REOPEN_AFTER = 10,
-};
-
-struct notify audio_output_client_notify;
-
-/**
- * Waits for command completion.
- *
- * @param ao the #audio_output instance; must be locked
- */
-static void ao_command_wait(struct audio_output *ao)
-{
- while (ao->command != AO_COMMAND_NONE) {
- g_mutex_unlock(ao->mutex);
- notify_wait(&audio_output_client_notify);
- g_mutex_lock(ao->mutex);
- }
-}
-
-/**
- * Sends a command to the #audio_output object, but does not wait for
- * completion.
- *
- * @param ao the #audio_output instance; must be locked
- */
-static void ao_command_async(struct audio_output *ao,
- enum audio_output_command cmd)
-{
- assert(ao->command == AO_COMMAND_NONE);
- ao->command = cmd;
- g_cond_signal(ao->cond);
-}
-
-/**
- * Sends a command to the #audio_output object and waits for
- * completion.
- *
- * @param ao the #audio_output instance; must be locked
- */
-static void
-ao_command(struct audio_output *ao, enum audio_output_command cmd)
-{
- ao_command_async(ao, cmd);
- ao_command_wait(ao);
-}
-
-/**
- * Lock the #audio_output object and execute the command
- * synchronously.
- */
-static void
-ao_lock_command(struct audio_output *ao, enum audio_output_command cmd)
-{
- g_mutex_lock(ao->mutex);
- ao_command(ao, cmd);
- g_mutex_unlock(ao->mutex);
-}
-
-void
-audio_output_enable(struct audio_output *ao)
-{
- if (ao->thread == NULL) {
- if (ao->plugin->enable == NULL) {
- /* don't bother to start the thread now if the
- device doesn't even have a enable() method;
- just assign the variable and we're done */
- ao->really_enabled = true;
- return;
- }
-
- audio_output_thread_start(ao);
- }
-
- ao_lock_command(ao, AO_COMMAND_ENABLE);
-}
-
-void
-audio_output_disable(struct audio_output *ao)
-{
- if (ao->thread == NULL) {
- if (ao->plugin->disable == NULL)
- ao->really_enabled = false;
- else
- /* if there's no thread yet, the device cannot
- be enabled */
- assert(!ao->really_enabled);
-
- return;
- }
-
- ao_lock_command(ao, AO_COMMAND_DISABLE);
-}
-
-/**
- * Object must be locked (and unlocked) by the caller.
- */
-static bool
-audio_output_open(struct audio_output *ao,
- const struct audio_format *audio_format,
- const struct music_pipe *mp)
-{
- bool open;
-
- assert(ao != NULL);
- assert(ao->allow_play);
- assert(audio_format_valid(audio_format));
- assert(mp != NULL);
-
- if (ao->fail_timer != NULL) {
- g_timer_destroy(ao->fail_timer);
- ao->fail_timer = NULL;
- }
-
- if (ao->open &&
- audio_format_equals(audio_format, &ao->in_audio_format)) {
- assert(ao->pipe == mp ||
- (ao->always_on && ao->pause));
-
- if (ao->pause) {
- ao->chunk = NULL;
- ao->pipe = mp;
-
- /* unpause with the CANCEL command; this is a
- hack, but suits well for forcing the thread
- to leave the ao_pause() thread, and we need
- to flush the device buffer anyway */
-
- /* we're not using audio_output_cancel() here,
- because that function is asynchronous */
- ao_command(ao, AO_COMMAND_CANCEL);
- }
-
- return true;
- }
-
- ao->in_audio_format = *audio_format;
- ao->chunk = NULL;
-
- ao->pipe = mp;
-
- if (ao->thread == NULL)
- audio_output_thread_start(ao);
-
- ao_command(ao, ao->open ? AO_COMMAND_REOPEN : AO_COMMAND_OPEN);
- open = ao->open;
-
- if (open && ao->mixer != NULL) {
- GError *error = NULL;
-
- if (!mixer_open(ao->mixer, &error)) {
- g_warning("Failed to open mixer for '%s': %s",
- ao->name, error->message);
- g_error_free(error);
- }
- }
-
- return open;
-}
-
-/**
- * Same as audio_output_close(), but expects the lock to be held by
- * the caller.
- */
-static void
-audio_output_close_locked(struct audio_output *ao)
-{
- assert(ao != NULL);
- assert(ao->allow_play);
-
- if (ao->mixer != NULL)
- mixer_auto_close(ao->mixer);
-
- assert(!ao->open || ao->fail_timer == NULL);
-
- if (ao->open)
- ao_command(ao, AO_COMMAND_CLOSE);
- else if (ao->fail_timer != NULL) {
- g_timer_destroy(ao->fail_timer);
- ao->fail_timer = NULL;
- }
-}
-
-bool
-audio_output_update(struct audio_output *ao,
- const struct audio_format *audio_format,
- const struct music_pipe *mp)
-{
- assert(mp != NULL);
-
- g_mutex_lock(ao->mutex);
-
- if (ao->enabled && ao->really_enabled) {
- if (ao->fail_timer == NULL ||
- g_timer_elapsed(ao->fail_timer, NULL) > REOPEN_AFTER) {
- bool success = audio_output_open(ao, audio_format, mp);
- g_mutex_unlock(ao->mutex);
- return success;
- }
- } else if (audio_output_is_open(ao))
- audio_output_close_locked(ao);
-
- g_mutex_unlock(ao->mutex);
- return false;
-}
-
-void
-audio_output_play(struct audio_output *ao)
-{
- g_mutex_lock(ao->mutex);
-
- assert(ao->allow_play);
-
- if (audio_output_is_open(ao))
- g_cond_signal(ao->cond);
-
- g_mutex_unlock(ao->mutex);
-}
-
-void audio_output_pause(struct audio_output *ao)
-{
- if (ao->mixer != NULL && ao->plugin->pause == NULL)
- /* the device has no pause mode: close the mixer,
- unless its "global" flag is set (checked by
- mixer_auto_close()) */
- mixer_auto_close(ao->mixer);
-
- g_mutex_lock(ao->mutex);
- assert(ao->allow_play);
- if (audio_output_is_open(ao))
- ao_command_async(ao, AO_COMMAND_PAUSE);
- g_mutex_unlock(ao->mutex);
-}
-
-void
-audio_output_drain_async(struct audio_output *ao)
-{
- g_mutex_lock(ao->mutex);
- assert(ao->allow_play);
- if (audio_output_is_open(ao))
- ao_command_async(ao, AO_COMMAND_DRAIN);
- g_mutex_unlock(ao->mutex);
-}
-
-void audio_output_cancel(struct audio_output *ao)
-{
- g_mutex_lock(ao->mutex);
-
- if (audio_output_is_open(ao)) {
- ao->allow_play = false;
- ao_command_async(ao, AO_COMMAND_CANCEL);
- }
-
- g_mutex_unlock(ao->mutex);
-}
-
-void
-audio_output_allow_play(struct audio_output *ao)
-{
- g_mutex_lock(ao->mutex);
-
- ao->allow_play = true;
- if (audio_output_is_open(ao))
- g_cond_signal(ao->cond);
-
- g_mutex_unlock(ao->mutex);
-}
-
-void
-audio_output_release(struct audio_output *ao)
-{
- if (ao->always_on)
- audio_output_pause(ao);
- else
- audio_output_close(ao);
-}
-
-void audio_output_close(struct audio_output *ao)
-{
- assert(ao != NULL);
- assert(!ao->open || ao->fail_timer == NULL);
-
- g_mutex_lock(ao->mutex);
- audio_output_close_locked(ao);
- g_mutex_unlock(ao->mutex);
-}
-
-void audio_output_finish(struct audio_output *ao)
-{
- audio_output_close(ao);
-
- assert(ao->fail_timer == NULL);
-
- if (ao->thread != NULL) {
- assert(ao->allow_play);
- ao_lock_command(ao, AO_COMMAND_KILL);
- g_thread_join(ao->thread);
- ao->thread = NULL;
- }
-
- audio_output_free(ao);
-}
diff --git a/src/output_control.h b/src/output_control.h
deleted file mode 100644
index 874a53518..000000000
--- a/src/output_control.h
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * 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_OUTPUT_CONTROL_H
-#define MPD_OUTPUT_CONTROL_H
-
-#include <glib.h>
-
-#include <stddef.h>
-#include <stdbool.h>
-
-struct audio_output;
-struct audio_format;
-struct config_param;
-struct music_pipe;
-struct player_control;
-
-static inline GQuark
-audio_output_quark(void)
-{
- return g_quark_from_static_string("audio_output");
-}
-
-/**
- * Enables the device.
- */
-void
-audio_output_enable(struct audio_output *ao);
-
-/**
- * Disables the device.
- */
-void
-audio_output_disable(struct audio_output *ao);
-
-/**
- * Opens or closes the device, depending on the "enabled" flag.
- *
- * @return true if the device is open
- */
-bool
-audio_output_update(struct audio_output *ao,
- const struct audio_format *audio_format,
- const struct music_pipe *mp);
-
-void
-audio_output_play(struct audio_output *ao);
-
-void audio_output_pause(struct audio_output *ao);
-
-void
-audio_output_drain_async(struct 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);
-
-/**
- * Set the "allow_play" and signal the thread.
- */
-void
-audio_output_allow_play(struct audio_output *ao);
-
-void audio_output_close(struct 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);
-
-void audio_output_finish(struct audio_output *ao);
-
-#endif
diff --git a/src/output_finish.c b/src/output_finish.c
deleted file mode 100644
index e11b43675..000000000
--- a/src/output_finish.c
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "output_internal.h"
-#include "output_plugin.h"
-#include "mixer_control.h"
-#include "filter_plugin.h"
-
-#include <assert.h>
-
-void
-ao_base_finish(struct audio_output *ao)
-{
- assert(!ao->open);
- assert(ao->fail_timer == NULL);
- assert(ao->thread == NULL);
-
- if (ao->mixer != NULL)
- mixer_free(ao->mixer);
-
- g_cond_free(ao->cond);
- g_mutex_free(ao->mutex);
-
- if (ao->replay_gain_filter != NULL)
- filter_free(ao->replay_gain_filter);
-
- if (ao->other_replay_gain_filter != NULL)
- filter_free(ao->other_replay_gain_filter);
-
- filter_free(ao->filter);
-
- pcm_buffer_deinit(&ao->cross_fade_buffer);
-}
-
-void
-audio_output_free(struct audio_output *ao)
-{
- assert(!ao->open);
- assert(ao->fail_timer == NULL);
- assert(ao->thread == NULL);
-
- ao_plugin_finish(ao);
-}
diff --git a/src/output_init.c b/src/output_init.c
deleted file mode 100644
index c3b808e94..000000000
--- a/src/output_init.c
+++ /dev/null
@@ -1,332 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "output_control.h"
-#include "output_api.h"
-#include "output_internal.h"
-#include "output_list.h"
-#include "audio_parser.h"
-#include "mixer_control.h"
-#include "mixer_type.h"
-#include "mixer_list.h"
-#include "mixer/software_mixer_plugin.h"
-#include "filter_plugin.h"
-#include "filter_registry.h"
-#include "filter_config.h"
-#include "filter/chain_filter_plugin.h"
-#include "filter/autoconvert_filter_plugin.h"
-#include "filter/replay_gain_filter_plugin.h"
-
-#include <glib.h>
-
-#include <assert.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "output"
-
-#define AUDIO_OUTPUT_TYPE "type"
-#define AUDIO_OUTPUT_NAME "name"
-#define AUDIO_OUTPUT_FORMAT "format"
-#define AUDIO_FILTERS "filters"
-
-static const struct audio_output_plugin *
-audio_output_detect(GError **error)
-{
- g_warning("Attempt to detect audio output device");
-
- audio_output_plugins_for_each(plugin) {
- if (plugin->test_default_device == NULL)
- continue;
-
- g_warning("Attempting to detect a %s audio device",
- plugin->name);
- if (ao_plugin_test_default_device(plugin))
- return plugin;
- }
-
- g_set_error(error, audio_output_quark(), 0,
- "Unable to detect an audio device");
- return NULL;
-}
-
-/**
- * Determines the mixer type which should be used for the specified
- * configuration block.
- *
- * This handles the deprecated options mixer_type (global) and
- * mixer_enabled, if the mixer_type setting is not configured.
- */
-static enum mixer_type
-audio_output_mixer_type(const struct config_param *param)
-{
- /* read the local "mixer_type" setting */
- const char *p = config_get_block_string(param, "mixer_type", NULL);
- if (p != NULL)
- return mixer_type_parse(p);
-
- /* try the local "mixer_enabled" setting next (deprecated) */
- if (!config_get_block_bool(param, "mixer_enabled", true))
- return MIXER_TYPE_NONE;
-
- /* fall back to the global "mixer_type" setting (also
- deprecated) */
- return mixer_type_parse(config_get_string("mixer_type", "hardware"));
-}
-
-static struct mixer *
-audio_output_load_mixer(struct audio_output *ao,
- const struct config_param *param,
- const struct mixer_plugin *plugin,
- struct filter *filter_chain,
- GError **error_r)
-{
- struct mixer *mixer;
-
- switch (audio_output_mixer_type(param)) {
- case MIXER_TYPE_NONE:
- case MIXER_TYPE_UNKNOWN:
- return NULL;
-
- case MIXER_TYPE_HARDWARE:
- if (plugin == NULL)
- return NULL;
-
- return mixer_new(plugin, ao, param, error_r);
-
- case MIXER_TYPE_SOFTWARE:
- mixer = mixer_new(&software_mixer_plugin, NULL, NULL, NULL);
- assert(mixer != NULL);
-
- filter_chain_append(filter_chain,
- software_mixer_get_filter(mixer));
- return mixer;
- }
-
- assert(false);
- return NULL;
-}
-
-bool
-ao_base_init(struct audio_output *ao,
- const struct audio_output_plugin *plugin,
- const struct config_param *param, GError **error_r)
-{
- assert(ao != NULL);
- assert(plugin != NULL);
- assert(plugin->finish != NULL);
- assert(plugin->open != NULL);
- assert(plugin->close != NULL);
- assert(plugin->play != NULL);
-
- GError *error = NULL;
-
- if (param) {
- const char *p;
-
- ao->name = config_get_block_string(param, AUDIO_OUTPUT_NAME,
- NULL);
- if (ao->name == NULL) {
- g_set_error(error_r, audio_output_quark(), 0,
- "Missing \"name\" configuration");
- return false;
- }
-
- p = config_get_block_string(param, AUDIO_OUTPUT_FORMAT,
- NULL);
- if (p != NULL) {
- bool success =
- audio_format_parse(&ao->config_audio_format,
- p, true, error_r);
- if (!success)
- return false;
- } else
- audio_format_clear(&ao->config_audio_format);
- } else {
- ao->name = "default detected output";
-
- audio_format_clear(&ao->config_audio_format);
- }
-
- ao->plugin = plugin;
- ao->always_on = config_get_block_bool(param, "always_on", false);
- ao->enabled = config_get_block_bool(param, "enabled", true);
- ao->really_enabled = false;
- ao->open = false;
- ao->pause = false;
- ao->allow_play = true;
- ao->fail_timer = NULL;
-
- pcm_buffer_init(&ao->cross_fade_buffer);
-
- /* set up the filter chain */
-
- ao->filter = filter_chain_new();
- assert(ao->filter != NULL);
-
- /* create the normalization filter (if configured) */
-
- if (config_get_bool(CONF_VOLUME_NORMALIZATION, false)) {
- struct filter *normalize_filter =
- filter_new(&normalize_filter_plugin, NULL, NULL);
- assert(normalize_filter != NULL);
-
- filter_chain_append(ao->filter,
- autoconvert_filter_new(normalize_filter));
- }
-
- filter_chain_parse(ao->filter,
- config_get_block_string(param, AUDIO_FILTERS, ""),
- &error
- );
-
- // It's not really fatal - Part of the filter chain has been set up already
- // and even an empty one will work (if only with unexpected behaviour)
- if (error != NULL) {
- g_warning("Failed to initialize filter chain for '%s': %s",
- ao->name, error->message);
- g_error_free(error);
- }
-
- ao->thread = NULL;
- ao->command = AO_COMMAND_NONE;
- ao->mutex = g_mutex_new();
- ao->cond = g_cond_new();
-
- ao->mixer = NULL;
- ao->replay_gain_filter = NULL;
- ao->other_replay_gain_filter = NULL;
-
- /* done */
-
- return true;
-}
-
-static bool
-audio_output_setup(struct audio_output *ao, const struct config_param *param,
- GError **error_r)
-{
-
- /* create the replay_gain filter */
-
- const char *replay_gain_handler =
- config_get_block_string(param, "replay_gain_handler",
- "software");
-
- if (strcmp(replay_gain_handler, "none") != 0) {
- ao->replay_gain_filter = filter_new(&replay_gain_filter_plugin,
- param, NULL);
- assert(ao->replay_gain_filter != NULL);
-
- ao->replay_gain_serial = 0;
-
- ao->other_replay_gain_filter = filter_new(&replay_gain_filter_plugin,
- param, NULL);
- assert(ao->other_replay_gain_filter != NULL);
-
- ao->other_replay_gain_serial = 0;
- } else {
- ao->replay_gain_filter = NULL;
- ao->other_replay_gain_filter = NULL;
- }
-
- /* set up the mixer */
-
- GError *error = NULL;
- ao->mixer = audio_output_load_mixer(ao, param,
- ao->plugin->mixer_plugin,
- ao->filter, &error);
- if (ao->mixer == NULL && error != NULL) {
- g_warning("Failed to initialize hardware mixer for '%s': %s",
- ao->name, error->message);
- g_error_free(error);
- }
-
- /* use the hardware mixer for replay gain? */
-
- if (strcmp(replay_gain_handler, "mixer") == 0) {
- if (ao->mixer != NULL)
- replay_gain_filter_set_mixer(ao->replay_gain_filter,
- ao->mixer, 100);
- else
- g_warning("No such mixer for output '%s'", ao->name);
- } else if (strcmp(replay_gain_handler, "software") != 0 &&
- ao->replay_gain_filter != NULL) {
- g_set_error(error_r, audio_output_quark(), 0,
- "Invalid \"replay_gain_handler\" value");
- return false;
- }
-
- /* the "convert" filter must be the last one in the chain */
-
- ao->convert_filter = filter_new(&convert_filter_plugin, NULL, NULL);
- assert(ao->convert_filter != NULL);
-
- filter_chain_append(ao->filter, ao->convert_filter);
-
- return true;
-}
-
-struct audio_output *
-audio_output_new(const struct config_param *param,
- struct player_control *pc,
- GError **error_r)
-{
- const struct audio_output_plugin *plugin;
-
- if (param) {
- const char *p;
-
- p = config_get_block_string(param, AUDIO_OUTPUT_TYPE, NULL);
- if (p == NULL) {
- g_set_error(error_r, audio_output_quark(), 0,
- "Missing \"type\" configuration");
- return false;
- }
-
- plugin = audio_output_plugin_get(p);
- if (plugin == NULL) {
- g_set_error(error_r, audio_output_quark(), 0,
- "No such audio output plugin: %s", p);
- return false;
- }
- } else {
- g_warning("No \"%s\" defined in config file\n",
- CONF_AUDIO_OUTPUT);
-
- plugin = audio_output_detect(error_r);
- if (plugin == NULL)
- return false;
-
- g_message("Successfully detected a %s audio device",
- plugin->name);
- }
-
- struct audio_output *ao = ao_plugin_init(plugin, param, error_r);
- if (ao == NULL)
- return NULL;
-
- if (!audio_output_setup(ao, param, error_r)) {
- ao_plugin_finish(ao);
- return NULL;
- }
-
- ao->player_control = pc;
- return ao;
-}
diff --git a/src/output_internal.h b/src/output_internal.h
deleted file mode 100644
index 9d975d789..000000000
--- a/src/output_internal.h
+++ /dev/null
@@ -1,269 +0,0 @@
-/*
- * 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_OUTPUT_INTERNAL_H
-#define MPD_OUTPUT_INTERNAL_H
-
-#include "audio_format.h"
-#include "pcm_buffer.h"
-
-#include <glib.h>
-
-#include <time.h>
-
-struct config_param;
-
-enum audio_output_command {
- AO_COMMAND_NONE = 0,
- AO_COMMAND_ENABLE,
- AO_COMMAND_DISABLE,
- AO_COMMAND_OPEN,
-
- /**
- * This command is invoked when the input audio format
- * changes.
- */
- AO_COMMAND_REOPEN,
-
- AO_COMMAND_CLOSE,
- AO_COMMAND_PAUSE,
-
- /**
- * Drains the internal (hardware) buffers of the device. This
- * operation may take a while to complete.
- */
- AO_COMMAND_DRAIN,
-
- AO_COMMAND_CANCEL,
- AO_COMMAND_KILL
-};
-
-struct audio_output {
- /**
- * The device's configured display name.
- */
- const char *name;
-
- /**
- * The plugin which implements this output device.
- */
- const struct audio_output_plugin *plugin;
-
- /**
- * The #mixer object associated with this audio output device.
- * May be NULL if none is available, or if software volume is
- * configured.
- */
- struct mixer *mixer;
-
- /**
- * Shall this output always play something (i.e. silence),
- * even when playback is stopped?
- */
- bool always_on;
-
- /**
- * Has the user enabled this device?
- */
- bool enabled;
-
- /**
- * Is this device actually enabled, i.e. the "enable" method
- * has succeeded?
- */
- bool really_enabled;
-
- /**
- * Is the device (already) open and functional?
- *
- * This attribute may only be modified by the output thread.
- * It is protected with #mutex: write accesses inside the
- * output thread and read accesses outside of it may only be
- * performed while the lock is held.
- */
- bool open;
-
- /**
- * Is the device paused? i.e. the output thread is in the
- * ao_pause() loop.
- */
- bool pause;
-
- /**
- * When this flag is set, the output thread will not do any
- * playback. It will wait until the flag is cleared.
- *
- * This is used to synchronize the "clear" operation on the
- * shared music pipe during the CANCEL command.
- */
- bool allow_play;
-
- /**
- * If not NULL, the device has failed, and this timer is used
- * to estimate how long it should stay disabled (unless
- * explicitly reopened with "play").
- */
- GTimer *fail_timer;
-
- /**
- * The configured audio format.
- */
- struct audio_format config_audio_format;
-
- /**
- * The audio_format in which audio data is received from the
- * player thread (which in turn receives it from the decoder).
- */
- struct audio_format in_audio_format;
-
- /**
- * The audio_format which is really sent to the device. This
- * is basically config_audio_format (if configured) or
- * in_audio_format, but may have been modified by
- * plugin->open().
- */
- struct audio_format out_audio_format;
-
- /**
- * The buffer used to allocate the cross-fading result.
- */
- struct pcm_buffer cross_fade_buffer;
-
- /**
- * The filter object of this audio output. This is an
- * instance of chain_filter_plugin.
- */
- struct filter *filter;
-
- /**
- * The replay_gain_filter_plugin instance of this audio
- * output.
- */
- struct filter *replay_gain_filter;
-
- /**
- * The serial number of the last replay gain info. 0 means no
- * replay gain info was available.
- */
- unsigned replay_gain_serial;
-
- /**
- * The replay_gain_filter_plugin instance of this audio
- * output, to be applied to the second chunk during
- * cross-fading.
- */
- struct filter *other_replay_gain_filter;
-
- /**
- * The serial number of the last replay gain info by the
- * "other" chunk during cross-fading.
- */
- unsigned other_replay_gain_serial;
-
- /**
- * The convert_filter_plugin instance of this audio output.
- * It is the last item in the filter chain, and is responsible
- * for converting the input data into the appropriate format
- * for this audio output.
- */
- struct filter *convert_filter;
-
- /**
- * The thread handle, or NULL if the output thread isn't
- * running.
- */
- GThread *thread;
-
- /**
- * The next command to be performed by the output thread.
- */
- enum audio_output_command command;
-
- /**
- * The music pipe which provides music chunks to be played.
- */
- const struct music_pipe *pipe;
-
- /**
- * This mutex protects #open, #fail_timer, #chunk and
- * #chunk_finished.
- */
- GMutex *mutex;
-
- /**
- * This condition object wakes up the output thread after
- * #command has been set.
- */
- GCond *cond;
-
- /**
- * The player_control object which "owns" this output. This
- * object is needed to signal command completion.
- */
- struct player_control *player_control;
-
- /**
- * The #music_chunk which is currently being played. All
- * chunks before this one may be returned to the
- * #music_buffer, because they are not going to be used by
- * this output anymore.
- */
- const struct music_chunk *chunk;
-
- /**
- * Has the output finished playing #chunk?
- */
- bool chunk_finished;
-};
-
-/**
- * Notify object used by the thread's client, i.e. we will send a
- * notify signal to this object, expecting the caller to wait on it.
- */
-extern struct notify audio_output_client_notify;
-
-static inline bool
-audio_output_is_open(const struct audio_output *ao)
-{
- return ao->open;
-}
-
-static inline bool
-audio_output_command_is_finished(const struct audio_output *ao)
-{
- return ao->command == AO_COMMAND_NONE;
-}
-
-struct audio_output *
-audio_output_new(const struct config_param *param,
- struct player_control *pc,
- GError **error_r);
-
-bool
-ao_base_init(struct audio_output *ao,
- const struct audio_output_plugin *plugin,
- const struct config_param *param, GError **error_r);
-
-void
-ao_base_finish(struct audio_output *ao);
-
-void
-audio_output_free(struct audio_output *ao);
-
-#endif
diff --git a/src/output_list.c b/src/output_list.c
deleted file mode 100644
index 835c02bba..000000000
--- a/src/output_list.c
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "output_list.h"
-#include "output_api.h"
-#include "output/alsa_output_plugin.h"
-#include "output/ao_output_plugin.h"
-#include "output/ffado_output_plugin.h"
-#include "output/fifo_output_plugin.h"
-#include "output/httpd_output_plugin.h"
-#include "output/jack_output_plugin.h"
-#include "output/mvp_output_plugin.h"
-#include "output/null_output_plugin.h"
-#include "output/openal_output_plugin.h"
-#include "output/oss_output_plugin.h"
-#include "output/osx_output_plugin.h"
-#include "output/pipe_output_plugin.h"
-#include "output/pulse_output_plugin.h"
-#include "output/recorder_output_plugin.h"
-#include "output/roar_output_plugin.h"
-#include "output/shout_output_plugin.h"
-#include "output/solaris_output_plugin.h"
-#include "output/winmm_output_plugin.h"
-
-const struct audio_output_plugin *const audio_output_plugins[] = {
-#ifdef HAVE_SHOUT
- &shout_output_plugin,
-#endif
- &null_output_plugin,
-#ifdef HAVE_FIFO
- &fifo_output_plugin,
-#endif
-#ifdef ENABLE_PIPE_OUTPUT
- &pipe_output_plugin,
-#endif
-#ifdef HAVE_ALSA
- &alsa_output_plugin,
-#endif
-#ifdef HAVE_ROAR
- &roar_output_plugin,
-#endif
-#ifdef HAVE_AO
- &ao_output_plugin,
-#endif
-#ifdef HAVE_OSS
- &oss_output_plugin,
-#endif
-#ifdef HAVE_OPENAL
- &openal_output_plugin,
-#endif
-#ifdef HAVE_OSX
- &osx_output_plugin,
-#endif
-#ifdef ENABLE_SOLARIS_OUTPUT
- &solaris_output_plugin,
-#endif
-#ifdef HAVE_PULSE
- &pulse_output_plugin,
-#endif
-#ifdef HAVE_MVP
- &mvp_output_plugin,
-#endif
-#ifdef HAVE_JACK
- &jack_output_plugin,
-#endif
-#ifdef ENABLE_HTTPD_OUTPUT
- &httpd_output_plugin,
-#endif
-#ifdef ENABLE_RECORDER_OUTPUT
- &recorder_output_plugin,
-#endif
-#ifdef ENABLE_WINMM_OUTPUT
- &winmm_output_plugin,
-#endif
-#ifdef ENABLE_FFADO_OUTPUT
- &ffado_output_plugin,
-#endif
- NULL
-};
-
-const struct audio_output_plugin *
-audio_output_plugin_get(const char *name)
-{
- audio_output_plugins_for_each(plugin)
- if (strcmp(plugin->name, name) == 0)
- return plugin;
-
- return NULL;
-}
diff --git a/src/output_list.h b/src/output_list.h
deleted file mode 100644
index 185ada716..000000000
--- a/src/output_list.h
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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_OUTPUT_LIST_H
-#define MPD_OUTPUT_LIST_H
-
-extern const struct audio_output_plugin *const audio_output_plugins[];
-
-const struct audio_output_plugin *
-audio_output_plugin_get(const char *name);
-
-#define audio_output_plugins_for_each(plugin) \
- for (const struct audio_output_plugin *plugin, \
- *const*output_plugin_iterator = &audio_output_plugins[0]; \
- (plugin = *output_plugin_iterator) != NULL; ++output_plugin_iterator)
-
-#endif
diff --git a/src/output_plugin.c b/src/output_plugin.c
deleted file mode 100644
index 221570c1c..000000000
--- a/src/output_plugin.c
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "output_plugin.h"
-#include "output_internal.h"
-
-struct audio_output *
-ao_plugin_init(const struct audio_output_plugin *plugin,
- const struct config_param *param,
- GError **error)
-{
- assert(plugin != NULL);
- assert(plugin->init != NULL);
-
- return plugin->init(param, error);
-}
-
-void
-ao_plugin_finish(struct audio_output *ao)
-{
- ao->plugin->finish(ao);
-}
-
-bool
-ao_plugin_enable(struct audio_output *ao, GError **error_r)
-{
- return ao->plugin->enable != NULL
- ? ao->plugin->enable(ao, error_r)
- : true;
-}
-
-void
-ao_plugin_disable(struct audio_output *ao)
-{
- if (ao->plugin->disable != NULL)
- ao->plugin->disable(ao);
-}
-
-bool
-ao_plugin_open(struct audio_output *ao, struct audio_format *audio_format,
- GError **error)
-{
- return ao->plugin->open(ao, audio_format, error);
-}
-
-void
-ao_plugin_close(struct audio_output *ao)
-{
- ao->plugin->close(ao);
-}
-
-unsigned
-ao_plugin_delay(struct audio_output *ao)
-{
- return ao->plugin->delay != NULL
- ? ao->plugin->delay(ao)
- : 0;
-}
-
-void
-ao_plugin_send_tag(struct audio_output *ao, const struct tag *tag)
-{
- if (ao->plugin->send_tag != NULL)
- ao->plugin->send_tag(ao, tag);
-}
-
-size_t
-ao_plugin_play(struct audio_output *ao, const void *chunk, size_t size,
- GError **error)
-{
- return ao->plugin->play(ao, chunk, size, error);
-}
-
-void
-ao_plugin_drain(struct audio_output *ao)
-{
- if (ao->plugin->drain != NULL)
- ao->plugin->drain(ao);
-}
-
-void
-ao_plugin_cancel(struct audio_output *ao)
-{
- if (ao->plugin->cancel != NULL)
- ao->plugin->cancel(ao);
-}
-
-bool
-ao_plugin_pause(struct audio_output *ao)
-{
- return ao->plugin->pause != NULL && ao->plugin->pause(ao);
-}
diff --git a/src/output_plugin.h b/src/output_plugin.h
deleted file mode 100644
index 209ca6221..000000000
--- a/src/output_plugin.h
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * 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_OUTPUT_PLUGIN_H
-#define MPD_OUTPUT_PLUGIN_H
-
-#include <glib.h>
-
-#include <stdbool.h>
-#include <stddef.h>
-
-struct config_param;
-struct audio_format;
-struct tag;
-
-/**
- * A plugin which controls an audio output device.
- */
-struct audio_output_plugin {
- /**
- * the plugin's name
- */
- const char *name;
-
- /**
- * Test if this plugin can provide a default output, in case
- * none has been configured. This method is optional.
- */
- bool (*test_default_device)(void);
-
- /**
- * Configure and initialize the device, but do not open it
- * yet.
- *
- * @param param the configuration section, or NULL if there is
- * no configuration
- * @param error location to store the error occurring, or NULL
- * to ignore errors
- * @return NULL on error, or an opaque pointer to the plugin's
- * data
- */
- struct audio_output *(*init)(const struct config_param *param,
- GError **error);
-
- /**
- * Free resources allocated by this device.
- */
- void (*finish)(struct audio_output *data);
-
- /**
- * Enable the device. This may allocate resources, preparing
- * for the device to be opened. Enabling a device cannot
- * fail: if an error occurs during that, it should be reported
- * by the open() method.
- *
- * @param error_r location to store the error occurring, or
- * NULL to ignore errors
- * @return true on success, false on error
- */
- bool (*enable)(struct audio_output *data, GError **error_r);
-
- /**
- * Disables the device. It is closed before this method is
- * called.
- */
- void (*disable)(struct audio_output *data);
-
- /**
- * Really open the device.
- *
- * @param audio_format the audio format in which data is going
- * to be delivered; may be modified by the plugin
- * @param error location to store the error occurring, or NULL
- * to ignore errors
- */
- bool (*open)(struct audio_output *data, struct audio_format *audio_format,
- GError **error);
-
- /**
- * Close the device.
- */
- void (*close)(struct audio_output *data);
-
- /**
- * Returns a positive number if the output thread shall delay
- * the next call to play() or pause(). This should be
- * implemented instead of doing a sleep inside the plugin,
- * because this allows MPD to listen to commands meanwhile.
- *
- * @return the number of milliseconds to wait
- */
- unsigned (*delay)(struct audio_output *data);
-
- /**
- * Display metadata for the next chunk. Optional method,
- * because not all devices can display metadata.
- */
- void (*send_tag)(struct audio_output *data, const struct tag *tag);
-
- /**
- * Play a chunk of audio data.
- *
- * @param error location to store the error occurring, or NULL
- * to ignore errors
- * @return the number of bytes played, or 0 on error
- */
- size_t (*play)(struct audio_output *data,
- const void *chunk, size_t size,
- GError **error);
-
- /**
- * Wait until the device has finished playing.
- */
- void (*drain)(struct audio_output *data);
-
- /**
- * Try to cancel data which may still be in the device's
- * buffers.
- */
- void (*cancel)(struct audio_output *data);
-
- /**
- * Pause the device. If supported, it may perform a special
- * action, which keeps the device open, but does not play
- * anything. Output plugins like "shout" might want to play
- * silence during pause, so their clients won't be
- * disconnected. Plugins which do not support pausing will
- * simply be closed, and have to be reopened when unpaused.
- *
- * @return false on error (output will be closed then), true
- * for continue to pause
- */
- bool (*pause)(struct audio_output *data);
-
- /**
- * The mixer plugin associated with this output plugin. This
- * may be NULL if no mixer plugin is implemented. When
- * created, this mixer plugin gets the same #config_param as
- * this audio output device.
- */
- const struct mixer_plugin *mixer_plugin;
-};
-
-static inline bool
-ao_plugin_test_default_device(const struct audio_output_plugin *plugin)
-{
- return plugin->test_default_device != NULL
- ? plugin->test_default_device()
- : false;
-}
-
-G_GNUC_MALLOC
-struct audio_output *
-ao_plugin_init(const struct audio_output_plugin *plugin,
- const struct config_param *param,
- GError **error);
-
-void
-ao_plugin_finish(struct audio_output *ao);
-
-bool
-ao_plugin_enable(struct audio_output *ao, GError **error_r);
-
-void
-ao_plugin_disable(struct audio_output *ao);
-
-bool
-ao_plugin_open(struct audio_output *ao, struct audio_format *audio_format,
- GError **error);
-
-void
-ao_plugin_close(struct audio_output *ao);
-
-G_GNUC_PURE
-unsigned
-ao_plugin_delay(struct audio_output *ao);
-
-void
-ao_plugin_send_tag(struct audio_output *ao, const struct tag *tag);
-
-size_t
-ao_plugin_play(struct audio_output *ao, const void *chunk, size_t size,
- GError **error);
-
-void
-ao_plugin_drain(struct audio_output *ao);
-
-void
-ao_plugin_cancel(struct audio_output *ao);
-
-bool
-ao_plugin_pause(struct audio_output *ao);
-
-#endif
diff --git a/src/output_print.c b/src/output_print.c
deleted file mode 100644
index 483648ca2..000000000
--- a/src/output_print.c
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * Protocol specific code for the audio output library.
- *
- */
-
-#include "config.h"
-#include "output_print.h"
-#include "output_internal.h"
-#include "output_all.h"
-#include "client.h"
-
-void
-printAudioDevices(struct client *client)
-{
- unsigned n = audio_output_count();
-
- for (unsigned i = 0; i < n; ++i) {
- const struct audio_output *ao = audio_output_get(i);
-
- client_printf(client,
- "outputid: %i\n"
- "outputname: %s\n"
- "outputenabled: %i\n",
- i, ao->name, ao->enabled);
- }
-}
diff --git a/src/output_print.h b/src/output_print.h
deleted file mode 100644
index e02f4e9f5..000000000
--- a/src/output_print.h
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * Protocol specific code for the audio output library.
- *
- */
-
-#ifndef OUTPUT_PRINT_H
-#define OUTPUT_PRINT_H
-
-struct client;
-
-void
-printAudioDevices(struct client *client);
-
-#endif
diff --git a/src/output_state.c b/src/output_state.c
deleted file mode 100644
index 7bcafb36b..000000000
--- a/src/output_state.c
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * Saving and loading the audio output states to/from the state file.
- *
- */
-
-#include "config.h"
-#include "output_state.h"
-#include "output_internal.h"
-#include "output_all.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <stdlib.h>
-#include <string.h>
-
-#define AUDIO_DEVICE_STATE "audio_device_state:"
-
-unsigned audio_output_state_version;
-
-void
-audio_output_state_save(FILE *fp)
-{
- unsigned n = audio_output_count();
-
- assert(n > 0);
-
- for (unsigned i = 0; i < n; ++i) {
- const struct audio_output *ao = audio_output_get(i);
-
- fprintf(fp, AUDIO_DEVICE_STATE "%d:%s\n",
- ao->enabled, ao->name);
- }
-}
-
-bool
-audio_output_state_read(const char *line)
-{
- long value;
- char *endptr;
- const char *name;
- struct audio_output *ao;
-
- if (!g_str_has_prefix(line, AUDIO_DEVICE_STATE))
- return false;
-
- line += sizeof(AUDIO_DEVICE_STATE) - 1;
-
- value = strtol(line, &endptr, 10);
- if (*endptr != ':' || (value != 0 && value != 1))
- return false;
-
- if (value != 0)
- /* state is "enabled": no-op */
- return true;
-
- name = endptr + 1;
- ao = audio_output_find(name);
- if (ao == NULL) {
- g_debug("Ignoring device state for '%s'", name);
- return true;
- }
-
- ao->enabled = false;
- return true;
-}
-
-unsigned
-audio_output_state_get_version(void)
-{
- return audio_output_state_version;
-}
diff --git a/src/output_state.h b/src/output_state.h
deleted file mode 100644
index 320a3520a..000000000
--- a/src/output_state.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * Saving and loading the audio output states to/from the state file.
- *
- */
-
-#ifndef OUTPUT_STATE_H
-#define OUTPUT_STATE_H
-
-#include <stdbool.h>
-#include <stdio.h>
-
-bool
-audio_output_state_read(const char *line);
-
-void
-audio_output_state_save(FILE *fp);
-
-/**
- * Generates a version number for the current state of the audio
- * outputs. This is used by timer_save_state_file() to determine
- * whether the state has changed and the state file should be saved.
- */
-unsigned
-audio_output_state_get_version(void);
-
-#endif
diff --git a/src/output_thread.c b/src/output_thread.c
deleted file mode 100644
index 4eef2ccdd..000000000
--- a/src/output_thread.c
+++ /dev/null
@@ -1,685 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "output_thread.h"
-#include "output_api.h"
-#include "output_internal.h"
-#include "chunk.h"
-#include "pipe.h"
-#include "player_control.h"
-#include "pcm_mix.h"
-#include "filter_plugin.h"
-#include "filter/convert_filter_plugin.h"
-#include "filter/replay_gain_filter_plugin.h"
-#include "mpd_error.h"
-#include "notify.h"
-#include "gcc.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <stdlib.h>
-#include <errno.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "output"
-
-static void ao_command_finished(struct audio_output *ao)
-{
- assert(ao->command != AO_COMMAND_NONE);
- ao->command = AO_COMMAND_NONE;
-
- g_mutex_unlock(ao->mutex);
- notify_signal(&audio_output_client_notify);
- g_mutex_lock(ao->mutex);
-}
-
-static bool
-ao_enable(struct audio_output *ao)
-{
- GError *error = NULL;
- bool success;
-
- if (ao->really_enabled)
- return true;
-
- g_mutex_unlock(ao->mutex);
- success = ao_plugin_enable(ao, &error);
- g_mutex_lock(ao->mutex);
- if (!success) {
- g_warning("Failed to enable \"%s\" [%s]: %s\n",
- ao->name, ao->plugin->name, error->message);
- g_error_free(error);
- return false;
- }
-
- ao->really_enabled = true;
- return true;
-}
-
-static void
-ao_close(struct audio_output *ao, bool drain);
-
-static void
-ao_disable(struct audio_output *ao)
-{
- if (ao->open)
- ao_close(ao, false);
-
- if (ao->really_enabled) {
- ao->really_enabled = false;
-
- g_mutex_unlock(ao->mutex);
- ao_plugin_disable(ao);
- g_mutex_lock(ao->mutex);
- }
-}
-
-static const struct audio_format *
-ao_filter_open(struct audio_output *ao,
- struct audio_format *audio_format,
- GError **error_r)
-{
- assert(audio_format_valid(audio_format));
-
- /* the replay_gain filter cannot fail here */
- if (ao->replay_gain_filter != NULL)
- filter_open(ao->replay_gain_filter, audio_format, error_r);
- if (ao->other_replay_gain_filter != NULL)
- filter_open(ao->other_replay_gain_filter, audio_format,
- error_r);
-
- const struct audio_format *af
- = filter_open(ao->filter, audio_format, error_r);
- if (af == NULL) {
- if (ao->replay_gain_filter != NULL)
- filter_close(ao->replay_gain_filter);
- if (ao->other_replay_gain_filter != NULL)
- filter_close(ao->other_replay_gain_filter);
- }
-
- return af;
-}
-
-static void
-ao_filter_close(struct audio_output *ao)
-{
- if (ao->replay_gain_filter != NULL)
- filter_close(ao->replay_gain_filter);
- if (ao->other_replay_gain_filter != NULL)
- filter_close(ao->other_replay_gain_filter);
-
- filter_close(ao->filter);
-}
-
-static void
-ao_open(struct audio_output *ao)
-{
- bool success;
- GError *error = NULL;
- const struct audio_format *filter_audio_format;
- struct audio_format_string af_string;
-
- assert(!ao->open);
- assert(ao->pipe != NULL);
- assert(ao->chunk == NULL);
- assert(audio_format_valid(&ao->in_audio_format));
-
- if (ao->fail_timer != NULL) {
- /* 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 = NULL;
- }
-
- /* enable the device (just in case the last enable has failed) */
-
- if (!ao_enable(ao))
- /* still no luck */
- return;
-
- /* open the filter */
-
- filter_audio_format = ao_filter_open(ao, &ao->in_audio_format, &error);
- if (filter_audio_format == NULL) {
- g_warning("Failed to open filter for \"%s\" [%s]: %s",
- ao->name, ao->plugin->name, error->message);
- g_error_free(error);
-
- ao->fail_timer = g_timer_new();
- return;
- }
-
- assert(audio_format_valid(filter_audio_format));
-
- ao->out_audio_format = *filter_audio_format;
- audio_format_mask_apply(&ao->out_audio_format,
- &ao->config_audio_format);
-
- g_mutex_unlock(ao->mutex);
- success = ao_plugin_open(ao, &ao->out_audio_format, &error);
- g_mutex_lock(ao->mutex);
-
- assert(!ao->open);
-
- if (!success) {
- g_warning("Failed to open \"%s\" [%s]: %s",
- ao->name, ao->plugin->name, error->message);
- g_error_free(error);
-
- ao_filter_close(ao);
- ao->fail_timer = g_timer_new();
- return;
- }
-
- convert_filter_set(ao->convert_filter, &ao->out_audio_format);
-
- ao->open = true;
-
- g_debug("opened plugin=%s name=\"%s\" "
- "audio_format=%s",
- ao->plugin->name, ao->name,
- audio_format_to_string(&ao->out_audio_format, &af_string));
-
- if (!audio_format_equals(&ao->in_audio_format,
- &ao->out_audio_format))
- g_debug("converting from %s",
- audio_format_to_string(&ao->in_audio_format,
- &af_string));
-}
-
-static void
-ao_close(struct audio_output *ao, bool drain)
-{
- assert(ao->open);
-
- ao->pipe = NULL;
-
- ao->chunk = NULL;
- ao->open = false;
-
- g_mutex_unlock(ao->mutex);
-
- if (drain)
- ao_plugin_drain(ao);
- else
- ao_plugin_cancel(ao);
-
- ao_plugin_close(ao);
- ao_filter_close(ao);
-
- g_mutex_lock(ao->mutex);
-
- g_debug("closed plugin=%s name=\"%s\"", ao->plugin->name, ao->name);
-}
-
-static void
-ao_reopen_filter(struct audio_output *ao)
-{
- const struct audio_format *filter_audio_format;
- GError *error = NULL;
-
- ao_filter_close(ao);
- filter_audio_format = ao_filter_open(ao, &ao->in_audio_format, &error);
- if (filter_audio_format == NULL) {
- g_warning("Failed to open filter for \"%s\" [%s]: %s",
- ao->name, ao->plugin->name, error->message);
- g_error_free(error);
-
- /* this is a little code duplication fro ao_close(),
- but we cannot call this function because we must
- not call filter_close(ao->filter) again */
-
- ao->pipe = NULL;
-
- ao->chunk = NULL;
- ao->open = false;
- ao->fail_timer = g_timer_new();
-
- g_mutex_unlock(ao->mutex);
- ao_plugin_close(ao);
- g_mutex_lock(ao->mutex);
-
- return;
- }
-
- convert_filter_set(ao->convert_filter, &ao->out_audio_format);
-}
-
-static void
-ao_reopen(struct audio_output *ao)
-{
- if (!audio_format_fully_defined(&ao->config_audio_format)) {
- if (ao->open) {
- const struct music_pipe *mp = ao->pipe;
- ao_close(ao, true);
- ao->pipe = mp;
- }
-
- /* no audio format is configured: copy in->out, let
- the output's open() method determine the effective
- out_audio_format */
- ao->out_audio_format = ao->in_audio_format;
- audio_format_mask_apply(&ao->out_audio_format,
- &ao->config_audio_format);
- }
-
- if (ao->open)
- /* the audio format has changed, and all filters have
- to be reconfigured */
- ao_reopen_filter(ao);
- else
- ao_open(ao);
-}
-
-/**
- * Wait until the output's delay reaches zero.
- *
- * @return true if playback should be continued, false if a command
- * was issued
- */
-static bool
-ao_wait(struct audio_output *ao)
-{
- while (true) {
- unsigned delay = ao_plugin_delay(ao);
- if (delay == 0)
- return true;
-
- GTimeVal tv;
- g_get_current_time(&tv);
- g_time_val_add(&tv, delay * 1000);
- (void)g_cond_timed_wait(ao->cond, ao->mutex, &tv);
-
- if (ao->command != AO_COMMAND_NONE)
- return false;
- }
-}
-
-static const char *
-ao_chunk_data(struct audio_output *ao, const struct music_chunk *chunk,
- struct filter *replay_gain_filter,
- unsigned *replay_gain_serial_p,
- size_t *length_r)
-{
- assert(chunk != NULL);
- assert(!music_chunk_is_empty(chunk));
- assert(music_chunk_check_format(chunk, &ao->in_audio_format));
-
- const char *data = chunk->data;
- size_t length = chunk->length;
-
- (void)ao;
-
- assert(length % audio_format_frame_size(&ao->in_audio_format) == 0);
-
- if (length > 0 && replay_gain_filter != NULL) {
- if (chunk->replay_gain_serial != *replay_gain_serial_p) {
- replay_gain_filter_set_info(replay_gain_filter,
- chunk->replay_gain_serial != 0
- ? &chunk->replay_gain_info
- : NULL);
- *replay_gain_serial_p = chunk->replay_gain_serial;
- }
-
- GError *error = NULL;
- data = filter_filter(replay_gain_filter, data, length,
- &length, &error);
- if (data == NULL) {
- g_warning("\"%s\" [%s] failed to filter: %s",
- ao->name, ao->plugin->name, error->message);
- g_error_free(error);
- return NULL;
- }
- }
-
- *length_r = length;
- return data;
-}
-
-static const char *
-ao_filter_chunk(struct audio_output *ao, const struct music_chunk *chunk,
- size_t *length_r)
-{
- GError *error = NULL;
-
- size_t length;
- const char *data = ao_chunk_data(ao, chunk, ao->replay_gain_filter,
- &ao->replay_gain_serial, &length);
- if (data == NULL)
- return NULL;
-
- if (length == 0) {
- /* empty chunk, nothing to do */
- *length_r = 0;
- return data;
- }
-
- /* cross-fade */
-
- if (chunk->other != NULL) {
- size_t other_length;
- const char *other_data =
- ao_chunk_data(ao, chunk->other,
- ao->other_replay_gain_filter,
- &ao->other_replay_gain_serial,
- &other_length);
- if (other_data == NULL)
- return NULL;
-
- if (other_length == 0) {
- *length_r = 0;
- return data;
- }
-
- /* if the "other" chunk is longer, then that trailer
- is used as-is, without mixing; it is part of the
- "next" song being faded in, and if there's a rest,
- it means cross-fading ends here */
-
- if (length > other_length)
- length = other_length;
-
- char *dest = pcm_buffer_get(&ao->cross_fade_buffer,
- other_length);
- memcpy(dest, other_data, other_length);
- if (!pcm_mix(dest, data, length, ao->in_audio_format.format,
- 1.0 - chunk->mix_ratio)) {
- g_warning("Cannot cross-fade format %s",
- sample_format_to_string(ao->in_audio_format.format));
- return NULL;
- }
-
- data = dest;
- length = other_length;
- }
-
- /* apply filter chain */
-
- data = filter_filter(ao->filter, data, length, &length, &error);
- if (data == NULL) {
- g_warning("\"%s\" [%s] failed to filter: %s",
- ao->name, ao->plugin->name, error->message);
- g_error_free(error);
- return NULL;
- }
-
- *length_r = length;
- return data;
-}
-
-static bool
-ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk)
-{
- GError *error = NULL;
-
- assert(ao != NULL);
- assert(ao->filter != NULL);
-
- if (chunk->tag != NULL) {
- g_mutex_unlock(ao->mutex);
- ao_plugin_send_tag(ao, chunk->tag);
- g_mutex_lock(ao->mutex);
- }
-
- size_t size;
-#if GCC_CHECK_VERSION(4,7)
- /* workaround -Wmaybe-uninitialized false positive */
- size = 0;
-#endif
- const char *data = ao_filter_chunk(ao, chunk, &size);
- if (data == NULL) {
- ao_close(ao, false);
-
- /* don't automatically reopen this device for 10
- seconds */
- ao->fail_timer = g_timer_new();
- return false;
- }
-
- while (size > 0 && ao->command == AO_COMMAND_NONE) {
- size_t nbytes;
-
- if (!ao_wait(ao))
- break;
-
- g_mutex_unlock(ao->mutex);
- nbytes = ao_plugin_play(ao, data, size, &error);
- g_mutex_lock(ao->mutex);
- if (nbytes == 0) {
- /* play()==0 means failure */
- g_warning("\"%s\" [%s] failed to play: %s",
- ao->name, ao->plugin->name, error->message);
- g_error_free(error);
-
- ao_close(ao, false);
-
- /* don't automatically reopen this device for
- 10 seconds */
- assert(ao->fail_timer == NULL);
- ao->fail_timer = g_timer_new();
-
- return false;
- }
-
- assert(nbytes <= size);
- assert(nbytes % audio_format_frame_size(&ao->out_audio_format) == 0);
-
- data += nbytes;
- size -= nbytes;
- }
-
- return true;
-}
-
-static const struct music_chunk *
-ao_next_chunk(struct audio_output *ao)
-{
- return ao->chunk != NULL
- /* continue the previous play() call */
- ? ao->chunk->next
- /* get the first chunk from the pipe */
- : music_pipe_peek(ao->pipe);
-}
-
-/**
- * Plays all remaining chunks, until the tail of the pipe has been
- * reached (and no more chunks are queued), or until a command is
- * received.
- *
- * @return true if at least one chunk has been available, false if the
- * tail of the pipe was already reached
- */
-static bool
-ao_play(struct audio_output *ao)
-{
- bool success;
- const struct music_chunk *chunk;
-
- assert(ao->pipe != NULL);
-
- chunk = ao_next_chunk(ao);
- if (chunk == NULL)
- /* no chunk available */
- return false;
-
- ao->chunk_finished = false;
-
- while (chunk != NULL && ao->command == AO_COMMAND_NONE) {
- assert(!ao->chunk_finished);
-
- ao->chunk = chunk;
-
- success = ao_play_chunk(ao, chunk);
- if (!success) {
- assert(ao->chunk == NULL);
- break;
- }
-
- assert(ao->chunk == chunk);
- chunk = chunk->next;
- }
-
- ao->chunk_finished = true;
-
- g_mutex_unlock(ao->mutex);
- player_lock_signal(ao->player_control);
- g_mutex_lock(ao->mutex);
-
- return true;
-}
-
-static void ao_pause(struct audio_output *ao)
-{
- bool ret;
-
- g_mutex_unlock(ao->mutex);
- ao_plugin_cancel(ao);
- g_mutex_lock(ao->mutex);
-
- ao->pause = true;
- ao_command_finished(ao);
-
- do {
- if (!ao_wait(ao))
- break;
-
- g_mutex_unlock(ao->mutex);
- ret = ao_plugin_pause(ao);
- g_mutex_lock(ao->mutex);
-
- if (!ret) {
- ao_close(ao, false);
- break;
- }
- } while (ao->command == AO_COMMAND_NONE);
-
- ao->pause = false;
-}
-
-static gpointer audio_output_task(gpointer arg)
-{
- struct audio_output *ao = arg;
-
- g_mutex_lock(ao->mutex);
-
- while (1) {
- switch (ao->command) {
- case AO_COMMAND_NONE:
- break;
-
- case AO_COMMAND_ENABLE:
- ao_enable(ao);
- ao_command_finished(ao);
- break;
-
- case AO_COMMAND_DISABLE:
- ao_disable(ao);
- ao_command_finished(ao);
- break;
-
- case AO_COMMAND_OPEN:
- ao_open(ao);
- ao_command_finished(ao);
- break;
-
- case AO_COMMAND_REOPEN:
- ao_reopen(ao);
- ao_command_finished(ao);
- break;
-
- case AO_COMMAND_CLOSE:
- assert(ao->open);
- assert(ao->pipe != NULL);
-
- ao_close(ao, false);
- ao_command_finished(ao);
- break;
-
- case AO_COMMAND_PAUSE:
- if (!ao->open) {
- /* the output has failed after
- audio_output_all_pause() has
- submitted the PAUSE command; bail
- out */
- ao_command_finished(ao);
- break;
- }
-
- ao_pause(ao);
- /* don't "break" here: this might cause
- ao_play() to be called when command==CLOSE
- ends the paused state - "continue" checks
- the new command first */
- continue;
-
- case AO_COMMAND_DRAIN:
- if (ao->open) {
- assert(ao->chunk == NULL);
- assert(music_pipe_peek(ao->pipe) == NULL);
-
- g_mutex_unlock(ao->mutex);
- ao_plugin_drain(ao);
- g_mutex_lock(ao->mutex);
- }
-
- ao_command_finished(ao);
- continue;
-
- case AO_COMMAND_CANCEL:
- ao->chunk = NULL;
-
- if (ao->open) {
- g_mutex_unlock(ao->mutex);
- ao_plugin_cancel(ao);
- g_mutex_lock(ao->mutex);
- }
-
- ao_command_finished(ao);
- continue;
-
- case AO_COMMAND_KILL:
- ao->chunk = NULL;
- ao_command_finished(ao);
- g_mutex_unlock(ao->mutex);
- return NULL;
- }
-
- if (ao->open && ao->allow_play && ao_play(ao))
- /* don't wait for an event if there are more
- chunks in the pipe */
- continue;
-
- if (ao->command == AO_COMMAND_NONE)
- g_cond_wait(ao->cond, ao->mutex);
- }
-}
-
-void audio_output_thread_start(struct audio_output *ao)
-{
- GError *e = NULL;
-
- assert(ao->command == AO_COMMAND_NONE);
-
- if (!(ao->thread = g_thread_create(audio_output_task, ao, true, &e)))
- MPD_ERROR("Failed to spawn output task: %s\n", e->message);
-}
diff --git a/src/output_thread.h b/src/output_thread.h
deleted file mode 100644
index 5ad9a7527..000000000
--- a/src/output_thread.h
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * 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_OUTPUT_THREAD_H
-#define MPD_OUTPUT_THREAD_H
-
-struct audio_output;
-
-void audio_output_thread_start(struct audio_output *ao);
-
-#endif
diff --git a/src/page.c b/src/page.c
deleted file mode 100644
index e2e22791f..000000000
--- a/src/page.c
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "page.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-
-/**
- * Allocates a new #page object, without filling the data element.
- */
-static struct page *
-page_new(size_t size)
-{
- struct page *page = g_malloc(sizeof(*page) + size -
- sizeof(page->data));
-
- assert(size > 0);
-
- page->ref = 1;
- page->size = size;
- return page;
-}
-
-struct page *
-page_new_copy(const void *data, size_t size)
-{
- struct page *page = page_new(size);
-
- assert(data != NULL);
-
- memcpy(page->data, data, size);
- return page;
-}
-
-struct page *
-page_new_concat(const struct page *a, const struct page *b)
-{
- struct page *page = page_new(a->size + b->size);
-
- memcpy(page->data, a->data, a->size);
- memcpy(page->data + a->size, b->data, b->size);
-
- return page;
-}
-
-void
-page_ref(struct page *page)
-{
- g_atomic_int_inc(&page->ref);
-}
-
-static void
-page_free(struct page *page)
-{
- assert(page->ref == 0);
-
- g_free(page);
-}
-
-bool
-page_unref(struct page *page)
-{
- bool unused = g_atomic_int_dec_and_test(&page->ref);
-
- if (unused)
- page_free(page);
-
- return unused;
-}
diff --git a/src/page.h b/src/page.h
deleted file mode 100644
index 8a3aaf396..000000000
--- a/src/page.h
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * 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.
- */
-
-/** \file
- *
- * This is a library which manages reference counted buffers.
- */
-
-#ifndef MPD_PAGE_H
-#define MPD_PAGE_H
-
-#include <stddef.h>
-#include <stdbool.h>
-
-/**
- * A dynamically allocated buffer which keeps track of its reference
- * count. This is useful for passing buffers around, when several
- * instances hold references to one buffer.
- */
-struct page {
- /**
- * The number of references to this buffer. This library uses
- * atomic functions to access it, i.e. no locks are required.
- * As soon as this attribute reaches zero, the buffer is
- * freed.
- */
- int ref;
-
- /**
- * The size of this buffer in bytes.
- */
- size_t size;
-
- /**
- * Dynamic array containing the buffer data.
- */
- unsigned char data[sizeof(long)];
-};
-
-/**
- * Creates a new #page object, and copies data from the specified
- * buffer. It is initialized with a reference count of 1.
- *
- * @param data the source buffer
- * @param size the size of the source buffer
- * @return the new #page object
- */
-struct page *
-page_new_copy(const void *data, size_t size);
-
-/**
- * Concatenates two pages to a new page.
- *
- * @param a the first page
- * @param b the second page, which is appended
- */
-struct page *
-page_new_concat(const struct page *a, const struct page *b);
-
-/**
- * Increases the reference counter.
- *
- * @param page the #page object
- */
-void
-page_ref(struct page *page);
-
-/**
- * Decreases the reference counter. If it reaches zero, the #page is
- * freed.
- *
- * @param page the #page object
- * @return true if the #page has been freed
- */
-bool
-page_unref(struct page *page);
-
-#endif
diff --git a/src/path.c b/src/path.c
deleted file mode 100644
index 59a91a0f7..000000000
--- a/src/path.c
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "path.h"
-#include "conf.h"
-#include "mpd_error.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-
-#ifdef G_OS_WIN32
-#include <windows.h> // for GetACP()
-#include <stdio.h> // for sprintf()
-#endif
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "path"
-
-static char *fs_charset;
-
-char *
-fs_charset_to_utf8(const char *path_fs)
-{
- return g_convert(path_fs, -1,
- "utf-8", fs_charset,
- NULL, NULL, NULL);
-}
-
-char *
-utf8_to_fs_charset(const char *path_utf8)
-{
- gchar *p;
-
- p = g_convert(path_utf8, -1,
- fs_charset, "utf-8",
- NULL, NULL, NULL);
- if (p == NULL)
- /* fall back to UTF-8 */
- p = g_strdup(path_utf8);
-
- return p;
-}
-
-static void
-path_set_fs_charset(const char *charset)
-{
- char *test;
-
- assert(charset != NULL);
-
- /* convert a space to ensure that the charset is valid */
- test = g_convert(" ", 1, charset, "UTF-8", NULL, NULL, NULL);
- if (test == NULL)
- MPD_ERROR("invalid filesystem charset: %s", charset);
- g_free(test);
-
- g_free(fs_charset);
- fs_charset = g_strdup(charset);
-
- g_debug("path_set_fs_charset: fs charset is: %s", fs_charset);
-}
-
-const char *path_get_fs_charset(void)
-{
- return fs_charset;
-}
-
-void path_global_init(void)
-{
- const char *charset = NULL;
-
- charset = config_get_string(CONF_FS_CHARSET, NULL);
- if (charset == NULL) {
-#ifndef G_OS_WIN32
- const gchar **encodings;
- g_get_filename_charsets(&encodings);
-
- if (encodings[0] != NULL && *encodings[0] != '\0')
- charset = encodings[0];
-#else /* G_OS_WIN32 */
- /* Glib claims that file system encoding is always utf-8
- * on native Win32 (i.e. not Cygwin).
- * However this is true only if <gstdio.h> helpers are used.
- * MPD uses regular <stdio.h> functions.
- * Those functions use encoding determined by GetACP(). */
- char win_charset[13];
- sprintf(win_charset, "cp%u", GetACP());
- charset = win_charset;
-#endif
- }
-
- if (charset) {
- path_set_fs_charset(charset);
- } else {
- g_message("setting filesystem charset to ISO-8859-1");
- path_set_fs_charset("ISO-8859-1");
- }
-}
-
-void path_global_finish(void)
-{
- g_free(fs_charset);
-}
diff --git a/src/path.h b/src/path.h
deleted file mode 100644
index 00c368e70..000000000
--- a/src/path.h
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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_PATH_H
-#define MPD_PATH_H
-
-#include <limits.h>
-
-#if !defined(MPD_PATH_MAX)
-# if defined(MAXPATHLEN)
-# define MPD_PATH_MAX MAXPATHLEN
-# elif defined(PATH_MAX)
-# define MPD_PATH_MAX PATH_MAX
-# else
-# define MPD_PATH_MAX 256
-# endif
-#endif
-
-void path_global_init(void);
-
-void path_global_finish(void);
-
-/**
- * Converts a file name in the filesystem charset to UTF-8. Returns
- * NULL on failure.
- */
-char *
-fs_charset_to_utf8(const char *path_fs);
-
-/**
- * Converts a file name in UTF-8 to the filesystem charset. Returns a
- * duplicate of the UTF-8 string on failure.
- */
-char *
-utf8_to_fs_charset(const char *path_utf8);
-
-const char *path_get_fs_charset(void);
-
-#endif
diff --git a/src/pcm/PcmBuffer.cxx b/src/pcm/PcmBuffer.cxx
new file mode 100644
index 000000000..a70888080
--- /dev/null
+++ b/src/pcm/PcmBuffer.cxx
@@ -0,0 +1,56 @@
+/*
+ * 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 "PcmBuffer.hxx"
+#include "poison.h"
+
+/**
+ * Align the specified size to the next 8k boundary.
+ */
+G_GNUC_CONST
+static size_t
+align_8k(size_t size)
+{
+ return ((size - 1) | 0x1fff) + 1;
+}
+
+void *
+PcmBuffer::Get(size_t new_size)
+{
+ if (new_size == 0)
+ /* never return NULL, because NULL would be assumed to
+ be an error condition */
+ new_size = 1;
+
+ if (size < new_size) {
+ /* free the old buffer */
+ g_free(buffer);
+
+ size = align_8k(new_size);
+ buffer = g_malloc(size);
+ } else {
+ /* discard old buffer contents */
+ poison_undefined(buffer, size);
+ }
+
+ assert(size >= new_size);
+
+ return buffer;
+}
diff --git a/src/pcm/PcmBuffer.hxx b/src/pcm/PcmBuffer.hxx
new file mode 100644
index 000000000..ae7030f76
--- /dev/null
+++ b/src/pcm/PcmBuffer.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 PCM_BUFFER_HXX
+#define PCM_BUFFER_HXX
+
+#include "check.h"
+#include "gcc.h"
+
+#include <glib.h>
+
+#include <assert.h>
+
+/**
+ * Manager for a temporary buffer which grows as needed. We could
+ * allocate a new buffer every time pcm_convert() is called, but that
+ * would put too much stress on the allocator.
+ */
+struct PcmBuffer {
+ void *buffer;
+
+ size_t size;
+
+ PcmBuffer():buffer(nullptr), size(0) {}
+
+ ~PcmBuffer() {
+ g_free(buffer);
+ }
+
+ void Clear() {
+ g_free(buffer);
+ buffer = nullptr;
+ size = 0;
+ }
+
+ /**
+ * Get the buffer, and guarantee a minimum size. This buffer becomes
+ * invalid with the next pcm_buffer_get() call.
+ *
+ * This function will never return NULL, even if size is zero, because
+ * the PCM library uses the NULL return value to signal "error". An
+ * empty destination buffer is not always an error.
+ */
+ gcc_malloc
+ void *Get(size_t size);
+};
+
+#endif
diff --git a/src/pcm/PcmChannels.cxx b/src/pcm/PcmChannels.cxx
new file mode 100644
index 000000000..27a155063
--- /dev/null
+++ b/src/pcm/PcmChannels.cxx
@@ -0,0 +1,290 @@
+/*
+ * 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 "PcmChannels.hxx"
+#include "PcmBuffer.hxx"
+#include "PcmUtils.hxx"
+
+#include <assert.h>
+
+template<typename D, typename S>
+static void
+MonoToStereo(D dest, S src, S end)
+{
+ while (src != end) {
+ const auto value = *src++;
+
+ *dest++ = value;
+ *dest++ = value;
+ }
+
+}
+
+static void
+pcm_convert_channels_16_2_to_1(int16_t *restrict dest,
+ const int16_t *restrict src,
+ const int16_t *restrict src_end)
+{
+ while (src < src_end) {
+ int32_t a = *src++, b = *src++;
+
+ *dest++ = (a + b) / 2;
+ }
+}
+
+static void
+pcm_convert_channels_16_n_to_2(int16_t *restrict dest,
+ unsigned src_channels,
+ const int16_t *restrict src,
+ const int16_t *restrict src_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;
+
+ /* XXX this is actually only mono ... */
+ *dest++ = value;
+ *dest++ = value;
+ }
+}
+
+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 NULL;
+
+ return dest;
+}
+
+static void
+pcm_convert_channels_24_2_to_1(int32_t *restrict dest,
+ const int32_t *restrict src,
+ const int32_t *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 *restrict dest,
+ unsigned src_channels,
+ const int32_t *restrict src,
+ const int32_t *restrict src_end)
+{
+ unsigned c;
+
+ assert(src_channels > 0);
+
+ while (src < src_end) {
+ int32_t sum = 0;
+ int32_t value;
+
+ for (c = 0; c < src_channels; ++c)
+ sum += *src++;
+ value = sum / (int)src_channels;
+
+ /* XXX 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 NULL;
+
+ return dest;
+}
+
+static void
+pcm_convert_channels_32_2_to_1(int32_t *restrict dest,
+ const int32_t *restrict src,
+ const int32_t *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)
+{
+ unsigned c;
+
+ assert(src_channels > 0);
+
+ while (src < src_end) {
+ int64_t sum = 0;
+ int32_t value;
+
+ for (c = 0; c < src_channels; ++c)
+ sum += *src++;
+ value = sum / (int64_t)src_channels;
+
+ /* XXX this is actually only mono ... */
+ *dest++ = value;
+ *dest++ = value;
+ }
+}
+
+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)
+{
+ 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_32_2_to_1(dest, src, src_end);
+ else if (dest_channels == 2)
+ pcm_convert_channels_32_n_to_2(dest, src_channels, src,
+ src_end);
+ else
+ return NULL;
+
+ return dest;
+}
+
+static void
+pcm_convert_channels_float_2_to_1(float *restrict dest,
+ const float *restrict src,
+ const float *restrict src_end)
+{
+ while (src < src_end) {
+ double a = *src++, b = *src++;
+
+ *dest++ = (a + b) / 2;
+ }
+}
+
+static void
+pcm_convert_channels_float_n_to_2(float *dest,
+ unsigned src_channels, const float *src,
+ const float *src_end)
+{
+ 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;
+
+ /* XXX this is actually only mono ... */
+ *dest++ = value;
+ *dest++ = value;
+ }
+}
+
+const 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)
+{
+ 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 NULL;
+
+ return dest;
+}
diff --git a/src/pcm/PcmChannels.hxx b/src/pcm/PcmChannels.hxx
new file mode 100644
index 000000000..889a14a60
--- /dev/null
+++ b/src/pcm/PcmChannels.hxx
@@ -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.
+ */
+
+#ifndef MPD_PCM_CHANNELS_HXX
+#define MPD_PCM_CHANNELS_HXX
+
+#include <stdint.h>
+#include <stddef.h>
+
+struct PcmBuffer;
+
+/**
+ * Changes the number of channels in 16 bit PCM data.
+ *
+ * @param buffer the destination pcm_buffer object
+ * @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 *
+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);
+
+/**
+ * Changes the number of channels in 24 bit PCM data (aligned at 32
+ * bit boundaries).
+ *
+ * @param buffer the destination pcm_buffer object
+ * @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 *
+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);
+
+/**
+ * Changes the number of channels in 32 bit PCM data.
+ *
+ * @param buffer the destination pcm_buffer object
+ * @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 *
+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);
+
+/**
+ * Changes the number of channels in 32 bit float PCM data.
+ *
+ * @param buffer the destination pcm_buffer object
+ * @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 *
+pcm_convert_channels_float(PcmBuffer &buffer,
+ unsigned dest_channels,
+ unsigned src_channels, const float *src,
+ size_t src_size, size_t *dest_size_r);
+
+#endif
diff --git a/src/pcm/PcmConvert.cxx b/src/pcm/PcmConvert.cxx
new file mode 100644
index 000000000..7e71da283
--- /dev/null
+++ b/src/pcm/PcmConvert.cxx
@@ -0,0 +1,309 @@
+/*
+ * 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.
+ */
+
+#include "config.h"
+#include "PcmConvert.hxx"
+#include "PcmChannels.hxx"
+#include "PcmFormat.hxx"
+#include "pcm_pack.h"
+#include "AudioFormat.hxx"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <math.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "pcm"
+
+PcmConvert::PcmConvert()
+{
+}
+
+PcmConvert::~PcmConvert()
+{
+}
+
+void
+PcmConvert::Reset()
+{
+ dsd.Reset();
+ resampler.Reset();
+}
+
+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,
+ GError **error_r)
+{
+ const int16_t *buf;
+ size_t len;
+
+ assert(dest_format.format == SampleFormat::S16);
+
+ buf = pcm_convert_to_16(format_buffer, dither,
+ src_format.format,
+ src_buffer, src_size,
+ &len);
+ if (buf == NULL) {
+ g_set_error(error_r, pcm_convert_quark(), 0,
+ "Conversion from %s to 16 bit is not implemented",
+ sample_format_to_string(src_format.format));
+ return NULL;
+ }
+
+ 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 == NULL) {
+ g_set_error(error_r, pcm_convert_quark(), 0,
+ "Conversion from %u to %u channels "
+ "is not implemented",
+ src_format.channels,
+ dest_format.channels);
+ return NULL;
+ }
+ }
+
+ 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_r);
+ if (buf == NULL)
+ return NULL;
+ }
+
+ *dest_size_r = len;
+ return buf;
+}
+
+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,
+ GError **error_r)
+{
+ 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 == NULL) {
+ g_set_error(error_r, pcm_convert_quark(), 0,
+ "Conversion from %s to 24 bit is not implemented",
+ sample_format_to_string(src_format.format));
+ return NULL;
+ }
+
+ 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 == NULL) {
+ g_set_error(error_r, pcm_convert_quark(), 0,
+ "Conversion from %u to %u channels "
+ "is not implemented",
+ src_format.channels,
+ dest_format.channels);
+ return NULL;
+ }
+ }
+
+ 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_r);
+ if (buf == NULL)
+ return NULL;
+ }
+
+ *dest_size_r = len;
+ return buf;
+}
+
+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,
+ GError **error_r)
+{
+ 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 == NULL) {
+ g_set_error(error_r, pcm_convert_quark(), 0,
+ "Conversion from %s to 32 bit is not implemented",
+ sample_format_to_string(src_format.format));
+ return NULL;
+ }
+
+ 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 == NULL) {
+ g_set_error(error_r, pcm_convert_quark(), 0,
+ "Conversion from %u to %u channels "
+ "is not implemented",
+ src_format.channels,
+ dest_format.channels);
+ return NULL;
+ }
+ }
+
+ 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_r);
+ if (buf == NULL)
+ return buf;
+ }
+
+ *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,
+ GError **error_r)
+{
+ const float *buffer = (const float *)src_buffer;
+ size_t size = src_size;
+
+ assert(dest_format.format == SampleFormat::FLOAT);
+
+ /* convert to float now */
+
+ buffer = pcm_convert_to_float(format_buffer,
+ src_format.format,
+ buffer, size, &size);
+ if (buffer == NULL) {
+ g_set_error(error_r, pcm_convert_quark(), 0,
+ "Conversion from %s to float is not implemented",
+ sample_format_to_string(src_format.format));
+ return NULL;
+ }
+
+ /* 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 == NULL) {
+ g_set_error(error_r, pcm_convert_quark(), 0,
+ "Conversion from %u to %u channels "
+ "is not implemented",
+ src_format.channels,
+ dest_format.channels);
+ return NULL;
+ }
+ }
+
+ /* 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_r);
+ if (buffer == NULL)
+ return NULL;
+ }
+
+ *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,
+ GError **error_r)
+{
+ 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 == NULL) {
+ g_set_error_literal(error_r, pcm_convert_quark(), 0,
+ "DSD to PCM conversion failed");
+ return NULL;
+ }
+
+ float_format = src_format;
+ float_format.format = SampleFormat::FLOAT;
+
+ src_format = float_format;
+ src = f;
+ src_size = f_size;
+ }
+
+ switch (dest_format.format) {
+ case SampleFormat::S16:
+ return Convert16(src_format, src, src_size,
+ dest_format, dest_size_r,
+ error_r);
+
+ case SampleFormat::S24_P32:
+ return Convert24(src_format, src, src_size,
+ dest_format, dest_size_r,
+ error_r);
+
+ case SampleFormat::S32:
+ return Convert32(src_format, src, src_size,
+ dest_format, dest_size_r,
+ error_r);
+
+ case SampleFormat::FLOAT:
+ return ConvertFloat(src_format, src, src_size,
+ dest_format, dest_size_r,
+ error_r);
+
+ default:
+ g_set_error(error_r, pcm_convert_quark(), 0,
+ "PCM conversion to %s is not implemented",
+ sample_format_to_string(dest_format.format));
+ return NULL;
+ }
+}
diff --git a/src/pcm/PcmConvert.hxx b/src/pcm/PcmConvert.hxx
new file mode 100644
index 000000000..42b59e407
--- /dev/null
+++ b/src/pcm/PcmConvert.hxx
@@ -0,0 +1,112 @@
+/*
+ * 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_CONVERT_HXX
+#define PCM_CONVERT_HXX
+
+#include "PcmDither.hxx"
+#include "PcmDsd.hxx"
+#include "PcmResample.hxx"
+#include "PcmBuffer.hxx"
+
+#include <glib.h>
+
+struct AudioFormat;
+
+/**
+ * This object is statically allocated (within another struct), and
+ * holds buffer allocations and the state for all kinds of PCM
+ * conversions.
+ */
+class PcmConvert {
+ PcmDsd dsd;
+
+ PcmResampler resampler;
+
+ PcmDither dither;
+
+ /** the buffer for converting the sample format */
+ PcmBuffer format_buffer;
+
+ /** the buffer for converting the channel count */
+ PcmBuffer channels_buffer;
+
+public:
+ PcmConvert();
+ ~PcmConvert();
+
+
+ /**
+ * Reset the pcm_convert_state object. Use this at the
+ * boundary between two distinct songs and each time the
+ * format changes.
+ */
+ void Reset();
+
+ /**
+ * Converts PCM data between two audio formats.
+ *
+ * @param src_format the source audio format
+ * @param src the source PCM buffer
+ * @param src_size the size of #src in bytes
+ * @param dest_format the requested destination audio format
+ * @param dest_size_r returns the number of bytes of the destination buffer
+ * @param error_r location to store the error occurring, or NULL to
+ * 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,
+ size_t *dest_size_r,
+ GError **error_r);
+
+private:
+ const int16_t *Convert16(AudioFormat src_format,
+ const void *src_buffer, size_t src_size,
+ AudioFormat dest_format,
+ size_t *dest_size_r,
+ GError **error_r);
+
+ const int32_t *Convert24(AudioFormat src_format,
+ const void *src_buffer, size_t src_size,
+ AudioFormat dest_format,
+ size_t *dest_size_r,
+ GError **error_r);
+
+ const int32_t *Convert32(AudioFormat src_format,
+ const void *src_buffer, size_t src_size,
+ AudioFormat dest_format,
+ size_t *dest_size_r,
+ GError **error_r);
+
+ const float *ConvertFloat(AudioFormat src_format,
+ const void *src_buffer, size_t src_size,
+ AudioFormat dest_format,
+ size_t *dest_size_r,
+ GError **error_r);
+};
+
+static inline GQuark
+pcm_convert_quark(void)
+{
+ return g_quark_from_static_string("pcm_convert");
+}
+
+#endif
diff --git a/src/pcm/PcmDither.cxx b/src/pcm/PcmDither.cxx
new file mode 100644
index 000000000..98d0d443e
--- /dev/null
+++ b/src/pcm/PcmDither.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 "PcmDither.hxx"
+#include "PcmPrng.hxx"
+
+inline int16_t
+PcmDither::Dither24To16(int_fast32_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;
+
+ sample += error[0] - error[1] + error[2];
+
+ error[2] = error[1];
+ error[1] = error[0] / 2;
+
+ /* round */
+ int_fast32_t output = sample + round;
+
+ int_fast32_t rnd = pcm_prng(random);
+ output += (rnd & mask) - (random & mask);
+
+ random = rnd;
+
+ /* clip */
+ if (output > MAX) {
+ output = MAX;
+
+ if (sample > MAX)
+ sample = MAX;
+ } else if (output < MIN) {
+ output = MIN;
+
+ if (sample < MIN)
+ sample = MIN;
+ }
+
+ output &= ~mask;
+
+ error[0] = sample - output;
+
+ return (int16_t)(output >> scale_bits);
+}
+
+void
+PcmDither::Dither24To16(int16_t *dest, const int32_t *src,
+ const int32_t *src_end)
+{
+ while (src < src_end)
+ *dest++ = Dither24To16(*src++);
+}
+
+inline int16_t
+PcmDither::Dither32To16(int_fast32_t sample)
+{
+ return Dither24To16(sample >> 8);
+}
+
+void
+PcmDither::Dither32To16(int16_t *dest, const int32_t *src,
+ const int32_t *src_end)
+{
+ while (src < src_end)
+ *dest++ = Dither32To16(*src++);
+}
diff --git a/src/pcm/PcmDither.hxx b/src/pcm/PcmDither.hxx
new file mode 100644
index 000000000..106382307
--- /dev/null
+++ b/src/pcm/PcmDither.hxx
@@ -0,0 +1,44 @@
+/*
+ * 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_DITHER_HXX
+#define MPD_PCM_DITHER_HXX
+
+#include <stdint.h>
+
+class PcmDither {
+ int32_t error[3];
+ int32_t random;
+
+public:
+ constexpr PcmDither()
+ :error{0, 0, 0}, random(0) {}
+
+ void Dither24To16(int16_t *dest, const int32_t *src,
+ const int32_t *src_end);
+
+ void Dither32To16(int16_t *dest, const int32_t *src,
+ const int32_t *src_end);
+
+private:
+ int16_t Dither24To16(int_fast32_t sample);
+ int16_t Dither32To16(int_fast32_t sample);
+};
+
+#endif
diff --git a/src/pcm/PcmDsd.cxx b/src/pcm/PcmDsd.cxx
new file mode 100644
index 000000000..e2f5aac20
--- /dev/null
+++ b/src/pcm/PcmDsd.cxx
@@ -0,0 +1,81 @@
+/*
+ * 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 "PcmDsd.hxx"
+#include "dsd2pcm/dsd2pcm.h"
+
+#include <glib.h>
+
+#include <algorithm>
+
+#include <string.h>
+
+PcmDsd::PcmDsd()
+{
+ std::fill_n(dsd2pcm, G_N_ELEMENTS(dsd2pcm), nullptr);
+}
+
+PcmDsd::~PcmDsd()
+{
+ for (unsigned i = 0; i < G_N_ELEMENTS(dsd2pcm); ++i)
+ if (dsd2pcm[i] != nullptr)
+ dsd2pcm_destroy(dsd2pcm[i]);
+}
+
+void
+PcmDsd::Reset()
+{
+ for (unsigned i = 0; i < G_N_ELEMENTS(dsd2pcm); ++i)
+ if (dsd2pcm[i] != nullptr)
+ dsd2pcm_reset(dsd2pcm[i]);
+}
+
+const float *
+PcmDsd::ToFloat(unsigned channels, bool lsbfirst,
+ const uint8_t *src, size_t src_size,
+ size_t *dest_size_r)
+{
+ assert(src != nullptr);
+ assert(src_size > 0);
+ assert(src_size % channels == 0);
+ assert(channels <= G_N_ELEMENTS(dsd2pcm));
+
+ 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) {
+ if (dsd2pcm[c] == nullptr) {
+ dsd2pcm[c] = dsd2pcm_init();
+ if (dsd2pcm[c] == nullptr)
+ return nullptr;
+ }
+
+ dsd2pcm_translate(dsd2pcm[c], num_frames,
+ src + c, channels,
+ lsbfirst, dest + c, channels);
+ }
+
+ return dest;
+}
diff --git a/src/pcm/PcmDsd.hxx b/src/pcm/PcmDsd.hxx
new file mode 100644
index 000000000..26ee11b13
--- /dev/null
+++ b/src/pcm/PcmDsd.hxx
@@ -0,0 +1,46 @@
+/*
+ * 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_DSD_HXX
+#define MPD_PCM_DSD_HXX
+
+#include "check.h"
+#include "PcmBuffer.hxx"
+
+#include <stdint.h>
+
+/**
+ * Wrapper for the dsd2pcm library.
+ */
+struct PcmDsd {
+ PcmBuffer buffer;
+
+ struct dsd2pcm_ctx_s *dsd2pcm[32];
+
+ PcmDsd();
+ ~PcmDsd();
+
+ void Reset();
+
+ const float *ToFloat(unsigned channels, bool lsbfirst,
+ const uint8_t *src, size_t src_size,
+ size_t *dest_size_r);
+};
+
+#endif
diff --git a/src/pcm/PcmDsdUsb.cxx b/src/pcm/PcmDsdUsb.cxx
new file mode 100644
index 000000000..7d58dec4d
--- /dev/null
+++ b/src/pcm/PcmDsdUsb.cxx
@@ -0,0 +1,96 @@
+/*
+ * 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 "PcmDsdUsb.hxx"
+#include "PcmBuffer.hxx"
+#include "AudioFormat.hxx"
+
+G_GNUC_CONST
+static inline uint32_t
+pcm_two_dsd_to_usb_marker1(uint8_t a, uint8_t b)
+{
+ return 0xff050000 | (a << 8) | b;
+}
+
+G_GNUC_CONST
+static inline uint32_t
+pcm_two_dsd_to_usb_marker2(uint8_t a, uint8_t b)
+{
+ return 0xfffa0000 | (a << 8) | b;
+}
+
+
+const uint32_t *
+pcm_dsd_to_usb(PcmBuffer &buffer, unsigned channels,
+ const uint8_t *src, size_t src_size,
+ size_t *dest_size_r)
+{
+ assert(audio_valid_channel_count(channels));
+ assert(src != NULL);
+ assert(src_size > 0);
+ assert(src_size % channels == 0);
+
+ const unsigned num_src_samples = src_size;
+ const unsigned num_src_frames = num_src_samples / channels;
+
+ /* this rounds down and discards the last odd frame; not
+ elegant, but good enough for now */
+ const unsigned num_frames = num_src_frames / 2;
+ const unsigned num_samples = num_frames * channels;
+
+ const size_t dest_size = num_samples * 4;
+ *dest_size_r = dest_size;
+ uint32_t *const dest0 = (uint32_t *)buffer.Get(dest_size),
+ *dest = dest0;
+
+ for (unsigned i = num_frames / 2; i > 0; --i) {
+ for (unsigned c = channels; c > 0; --c) {
+ /* each 24 bit sample has 16 DSD sample bits
+ plus the magic 0x05 marker */
+
+ *dest++ = pcm_two_dsd_to_usb_marker1(src[0], src[channels]);
+
+ /* seek the source pointer to the next
+ channel */
+ ++src;
+ }
+
+ /* skip the second byte of each channel, because we
+ have already copied it */
+ src += channels;
+
+ for (unsigned c = channels; c > 0; --c) {
+ /* each 24 bit sample has 16 DSD sample bits
+ plus the magic 0xfa marker */
+
+ *dest++ = pcm_two_dsd_to_usb_marker2(src[0], src[channels]);
+
+ /* seek the source pointer to the next
+ channel */
+ ++src;
+ }
+
+ /* skip the second byte of each channel, because we
+ have already copied it */
+ src += channels;
+ }
+
+ return dest0;
+}
diff --git a/src/pcm/PcmDsdUsb.hxx b/src/pcm/PcmDsdUsb.hxx
new file mode 100644
index 000000000..2cf8bfbba
--- /dev/null
+++ b/src/pcm/PcmDsdUsb.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_PCM_DSD_USB_HXX
+#define MPD_PCM_DSD_USB_HXX
+
+#include "check.h"
+
+#include <stdint.h>
+#include <stddef.h>
+
+struct PcmBuffer;
+
+/**
+ * Pack DSD 1 bit samples into (padded) 24 bit PCM samples for
+ * playback over USB, according to the proposed standard by
+ * dCS and others:
+ * http://www.sonore.us/DoP_openStandard_1v1.pdf
+ */
+const uint32_t *
+pcm_dsd_to_usb(PcmBuffer &buffer, unsigned channels,
+ const uint8_t *src, size_t src_size,
+ size_t *dest_size_r);
+
+#endif
diff --git a/src/pcm/PcmExport.cxx b/src/pcm/PcmExport.cxx
new file mode 100644
index 000000000..762411f59
--- /dev/null
+++ b/src/pcm/PcmExport.cxx
@@ -0,0 +1,145 @@
+/*
+ * 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 "PcmExport.hxx"
+#include "PcmDsdUsb.hxx"
+
+extern "C" {
+#include "pcm_pack.h"
+#include "util/byte_reverse.h"
+}
+
+void
+PcmExport::Open(SampleFormat sample_format, unsigned _channels,
+ bool _dsd_usb, bool _shift8, bool _pack, bool _reverse_endian)
+{
+ assert(audio_valid_sample_format(sample_format));
+ assert(!_dsd_usb || audio_valid_channel_count(_channels));
+
+ channels = _channels;
+ dsd_usb = _dsd_usb && sample_format == SampleFormat::DSD;
+ if (dsd_usb)
+ /* after the conversion to DSD-over-USB, the DSD
+ samples are stuffed inside fake 24 bit samples */
+ sample_format = SampleFormat::S24_P32;
+
+ shift8 = _shift8 && sample_format == SampleFormat::S24_P32;
+ pack24 = _pack && sample_format == SampleFormat::S24_P32;
+
+ assert(!shift8 || !pack24);
+
+ reverse_endian = 0;
+ if (_reverse_endian) {
+ size_t sample_size = pack24
+ ? 3
+ : sample_format_size(sample_format);
+ assert(sample_size <= 0xff);
+
+ if (sample_size > 1)
+ reverse_endian = sample_size;
+ }
+}
+
+size_t
+PcmExport::GetFrameSize(const AudioFormat &audio_format) const
+{
+ if (pack24)
+ /* packed 24 bit samples (3 bytes per sample) */
+ return audio_format.channels * 3;
+
+ if (dsd_usb)
+ /* the DSD-over-USB draft says that DSD 1-bit samples
+ are enclosed within 24 bit samples, and MPD's
+ representation of 24 bit is padded to 32 bit (4
+ bytes per sample) */
+ return channels * 4;
+
+ return audio_format.GetFrameSize();
+}
+
+const void *
+PcmExport::Export(const void *data, size_t size, size_t &dest_size_r)
+{
+ if (dsd_usb)
+ data = pcm_dsd_to_usb(dsd_buffer, channels,
+ (const uint8_t *)data, size, &size);
+
+ if (pack24) {
+ assert(size % 4 == 0);
+
+ const size_t num_samples = size / 4;
+ const size_t dest_size = num_samples * 3;
+
+ const uint8_t *src8 = (const uint8_t *)data;
+ const uint8_t *src_end8 = src8 + size;
+ uint8_t *dest = (uint8_t *)pack_buffer.Get(dest_size);
+ assert(dest != NULL);
+
+ pcm_pack_24(dest, (const int32_t *)src8,
+ (const int32_t *)src_end8);
+
+ data = dest;
+ size = dest_size;
+ } else if (shift8) {
+ assert(size % 4 == 0);
+
+ const uint8_t *src8 = (const uint8_t *)data;
+ const uint8_t *src_end8 = src8 + size;
+ const uint32_t *src = (const uint32_t *)src8;
+ const uint32_t *const src_end = (const uint32_t *)src_end8;
+
+ uint32_t *dest = (uint32_t *)pack_buffer.Get(size);
+ data = dest;
+
+ while (src < src_end)
+ *dest++ = *src++ << 8;
+ }
+
+
+ if (reverse_endian > 0) {
+ assert(reverse_endian >= 2);
+
+ uint8_t *dest = (uint8_t *)reverse_buffer.Get(size);
+ assert(dest != NULL);
+
+ const uint8_t *src = (const uint8_t *)data;
+ const uint8_t *src_end = src + size;
+ reverse_bytes(dest, src, src_end, reverse_endian);
+
+ data = dest;
+ }
+
+ dest_size_r = size;
+ return data;
+}
+
+size_t
+PcmExport::CalcSourceSize(size_t size) const
+{
+ if (pack24)
+ /* 32 bit to 24 bit conversion (4 to 3 bytes) */
+ size = (size / 3) * 4;
+
+ if (dsd_usb)
+ /* DSD over USB doubles the transport size */
+ size /= 2;
+
+ return size;
+}
diff --git a/src/pcm/PcmExport.hxx b/src/pcm/PcmExport.hxx
new file mode 100644
index 000000000..bd18c0534
--- /dev/null
+++ b/src/pcm/PcmExport.hxx
@@ -0,0 +1,127 @@
+/*
+ * 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_EXPORT_HXX
+#define PCM_EXPORT_HXX
+
+#include "check.h"
+#include "PcmBuffer.hxx"
+#include "AudioFormat.hxx"
+
+struct AudioFormat;
+
+/**
+ * An object that handles export of PCM samples to some instance
+ * outside of MPD. It has a few more options to tweak the binary
+ * representation which are not supported by the pcm_convert library.
+ */
+struct PcmExport {
+ /**
+ * The buffer is used to convert DSD samples to the
+ * DSD-over-USB format.
+ *
+ * @see #dsd_usb
+ */
+ PcmBuffer dsd_buffer;
+
+ /**
+ * The buffer is used to pack samples, removing padding.
+ *
+ * @see #pack24
+ */
+ PcmBuffer pack_buffer;
+
+ /**
+ * The buffer is used to reverse the byte order.
+ *
+ * @see #reverse_endian
+ */
+ PcmBuffer reverse_buffer;
+
+ /**
+ * The number of channels.
+ */
+ uint8_t channels;
+
+ /**
+ * Convert DSD to DSD-over-USB? Input format must be
+ * SampleFormat::DSD and output format must be
+ * SampleFormat::S24_P32.
+ */
+ bool dsd_usb;
+
+ /**
+ * Convert (padded) 24 bit samples to 32 bit by shifting 8
+ * bits to the left?
+ */
+ bool shift8;
+
+ /**
+ * Pack 24 bit samples?
+ */
+ bool pack24;
+
+ /**
+ * Export the samples in reverse byte order? A non-zero value
+ * means the option is enabled and represents the size of each
+ * sample (2 or bigger).
+ */
+ uint8_t reverse_endian;
+
+ /**
+ * Open the #pcm_export_state object.
+ *
+ * There is no "close" method. This function may be called multiple
+ * times to reuse the object, until pcm_export_deinit() is called.
+ *
+ * This function cannot fail.
+ *
+ * @param channels the number of channels; ignored unless dsd_usb is set
+ */
+ void Open(SampleFormat sample_format, unsigned channels,
+ bool dsd_usb, bool shift8, bool pack, bool reverse_endian);
+
+ /**
+ * Calculate the size of one output frame.
+ */
+ gcc_pure
+ size_t GetFrameSize(const AudioFormat &audio_format) const;
+
+ /**
+ * Export a PCM buffer.
+ *
+ * @param state an initialized and open pcm_export_state object
+ * @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 (may be a pointer to the source buffer)
+ */
+ const void *Export(const void *src, size_t src_size,
+ size_t &dest_size_r);
+
+ /**
+ * Converts the number of consumed bytes from the pcm_export()
+ * destination buffer to the according number of bytes from the
+ * pcm_export() source buffer.
+ */
+ gcc_pure
+ size_t CalcSourceSize(size_t dest_size) const;
+};
+
+#endif
diff --git a/src/pcm/PcmFormat.cxx b/src/pcm/PcmFormat.cxx
new file mode 100644
index 000000000..6425c7cfd
--- /dev/null
+++ b/src/pcm/PcmFormat.cxx
@@ -0,0 +1,500 @@
+/*
+ * 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.
+ */
+
+#include "config.h"
+#include "PcmFormat.hxx"
+#include "PcmDither.hxx"
+#include "PcmBuffer.hxx"
+#include "pcm_pack.h"
+#include "PcmUtils.hxx"
+
+#include <type_traits>
+
+template<typename S>
+struct DefaultSampleBits {
+ typedef decltype(*S()) T;
+ typedef typename std::remove_reference<T>::type U;
+
+ static constexpr auto value = sizeof(U) * 8;
+};
+
+static void
+pcm_convert_8_to_16(int16_t *out, const int8_t *in, const int8_t *in_end)
+{
+ while (in < in_end) {
+ *out++ = *in++ << 8;
+ }
+}
+
+static void
+pcm_convert_24_to_16(PcmDither &dither,
+ int16_t *out, const int32_t *in, const int32_t *in_end)
+{
+ dither.Dither24To16(out, in, in_end);
+}
+
+static void
+pcm_convert_32_to_16(PcmDither &dither,
+ int16_t *out, const int32_t *in, const int32_t *in_end)
+{
+ dither.Dither32To16(out, in, in_end);
+}
+
+template<typename S, unsigned bits=DefaultSampleBits<S>::value>
+static void
+ConvertFromFloat(S dest, const float *src, const float *end)
+{
+ typedef decltype(*S()) T;
+ typedef typename std::remove_reference<T>::type U;
+
+ const float factor = 1 << (bits - 1);
+
+ while (src != end) {
+ int sample(*src++ * factor);
+ *dest++ = PcmClamp<U, int, bits>(sample);
+ }
+}
+
+template<typename S, unsigned bits=DefaultSampleBits<S>::value>
+static void
+ConvertFromFloat(S dest, const float *src, size_t size)
+{
+ ConvertFromFloat<S, bits>(dest, src, pcm_end_pointer(src, size));
+}
+
+template<typename S, unsigned bits=sizeof(S)*8>
+static S *
+AllocateFromFloat(PcmBuffer &buffer, const float *src, size_t src_size,
+ size_t *dest_size_r)
+{
+ constexpr size_t src_sample_size = sizeof(*src);
+ assert(src_size % src_sample_size == 0);
+
+ const size_t num_samples = src_size / src_sample_size;
+ *dest_size_r = num_samples * sizeof(S);
+ S *dest = (S *)buffer.Get(*dest_size_r);
+ ConvertFromFloat<S *, bits>(dest, src, src_size);
+ 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)
+{
+ 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;
+}
+
+static 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;
+}
+
+static 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;
+}
+
+static int16_t *
+pcm_allocate_float_to_16(PcmBuffer &buffer,
+ const float *src, size_t src_size,
+ size_t *dest_size_r)
+{
+ return AllocateFromFloat<int16_t>(buffer, src, src_size, dest_size_r);
+}
+
+const 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)
+{
+ assert(src_size % sample_format_size(src_format) == 0);
+
+ switch (src_format) {
+ case SampleFormat::UNDEFINED:
+ case SampleFormat::DSD:
+ break;
+
+ case SampleFormat::S8:
+ return pcm_allocate_8_to_16(buffer,
+ (const int8_t *)src, src_size,
+ dest_size_r);
+
+ case SampleFormat::S16:
+ *dest_size_r = src_size;
+ return (const int16_t *)src;
+
+ case SampleFormat::S24_P32:
+ return pcm_allocate_24p32_to_16(buffer, dither,
+ (const int32_t *)src, src_size,
+ dest_size_r);
+
+ case SampleFormat::S32:
+ return pcm_allocate_32_to_16(buffer, dither,
+ (const int32_t *)src, src_size,
+ dest_size_r);
+
+ case SampleFormat::FLOAT:
+ return pcm_allocate_float_to_16(buffer,
+ (const float *)src, src_size,
+ dest_size_r);
+ }
+
+ return NULL;
+}
+
+static void
+pcm_convert_8_to_24(int32_t *out, const int8_t *in, const int8_t *in_end)
+{
+ while (in < in_end)
+ *out++ = *in++ << 16;
+}
+
+static void
+pcm_convert_16_to_24(int32_t *out, const int16_t *in, const int16_t *in_end)
+{
+ while (in < in_end)
+ *out++ = *in++ << 8;
+}
+
+static void
+pcm_convert_32_to_24(int32_t *restrict out,
+ const int32_t *restrict in,
+ const int32_t *restrict in_end)
+{
+ while (in < in_end)
+ *out++ = *in++ >> 8;
+}
+
+static int32_t *
+pcm_allocate_8_to_24(PcmBuffer &buffer,
+ const int8_t *src, size_t src_size, size_t *dest_size_r)
+{
+ 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;
+}
+
+static int32_t *
+pcm_allocate_16_to_24(PcmBuffer &buffer,
+ const int16_t *src, size_t src_size, size_t *dest_size_r)
+{
+ 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;
+}
+
+static int32_t *
+pcm_allocate_32_to_24(PcmBuffer &buffer,
+ const int32_t *src, size_t src_size, size_t *dest_size_r)
+{
+ *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;
+}
+
+static int32_t *
+pcm_allocate_float_to_24(PcmBuffer &buffer,
+ const float *src, size_t src_size,
+ size_t *dest_size_r)
+{
+ return AllocateFromFloat<int32_t, 24>(buffer, src, src_size,
+ dest_size_r);
+}
+
+const int32_t *
+pcm_convert_to_24(PcmBuffer &buffer,
+ SampleFormat src_format, const void *src,
+ size_t src_size, size_t *dest_size_r)
+{
+ assert(src_size % sample_format_size(src_format) == 0);
+
+ switch (src_format) {
+ case SampleFormat::UNDEFINED:
+ case SampleFormat::DSD:
+ break;
+
+ case SampleFormat::S8:
+ return pcm_allocate_8_to_24(buffer,
+ (const int8_t *)src, src_size,
+ dest_size_r);
+
+ case SampleFormat::S16:
+ return pcm_allocate_16_to_24(buffer,
+ (const int16_t *)src, src_size,
+ dest_size_r);
+
+ case SampleFormat::S24_P32:
+ *dest_size_r = src_size;
+ return (const int32_t *)src;
+
+ case SampleFormat::S32:
+ return pcm_allocate_32_to_24(buffer,
+ (const int32_t *)src, src_size,
+ dest_size_r);
+
+ case SampleFormat::FLOAT:
+ return pcm_allocate_float_to_24(buffer,
+ (const float *)src, src_size,
+ dest_size_r);
+ }
+
+ return NULL;
+}
+
+static void
+pcm_convert_8_to_32(int32_t *out, const int8_t *in, const int8_t *in_end)
+{
+ while (in < in_end)
+ *out++ = *in++ << 24;
+}
+
+static void
+pcm_convert_16_to_32(int32_t *out, const int16_t *in, const int16_t *in_end)
+{
+ while (in < in_end)
+ *out++ = *in++ << 16;
+}
+
+static void
+pcm_convert_24_to_32(int32_t *restrict out,
+ const int32_t *restrict in,
+ const int32_t *restrict in_end)
+{
+ while (in < in_end)
+ *out++ = *in++ << 8;
+}
+
+static int32_t *
+pcm_allocate_8_to_32(PcmBuffer &buffer,
+ const int8_t *src, size_t src_size, size_t *dest_size_r)
+{
+ 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;
+}
+
+static int32_t *
+pcm_allocate_16_to_32(PcmBuffer &buffer,
+ const int16_t *src, size_t src_size, size_t *dest_size_r)
+{
+ 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;
+}
+
+static int32_t *
+pcm_allocate_24p32_to_32(PcmBuffer &buffer,
+ const int32_t *src, size_t src_size,
+ size_t *dest_size_r)
+{
+ *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;
+}
+
+static int32_t *
+pcm_allocate_float_to_32(PcmBuffer &buffer,
+ const float *src, size_t src_size,
+ size_t *dest_size_r)
+{
+ /* convert to S24_P32 first */
+ int32_t *dest = pcm_allocate_float_to_24(buffer, src, src_size,
+ dest_size_r);
+
+ /* convert to 32 bit in-place */
+ pcm_convert_24_to_32(dest, dest, pcm_end_pointer(dest, *dest_size_r));
+ return dest;
+}
+
+const int32_t *
+pcm_convert_to_32(PcmBuffer &buffer,
+ SampleFormat src_format, const void *src,
+ size_t src_size, size_t *dest_size_r)
+{
+ assert(src_size % sample_format_size(src_format) == 0);
+
+ switch (src_format) {
+ case SampleFormat::UNDEFINED:
+ case SampleFormat::DSD:
+ break;
+
+ case SampleFormat::S8:
+ return pcm_allocate_8_to_32(buffer,
+ (const int8_t *)src, src_size,
+ dest_size_r);
+
+ case SampleFormat::S16:
+ return pcm_allocate_16_to_32(buffer,
+ (const int16_t *)src, src_size,
+ dest_size_r);
+
+ case SampleFormat::S24_P32:
+ return pcm_allocate_24p32_to_32(buffer,
+ (const int32_t *)src, src_size,
+ dest_size_r);
+
+ case SampleFormat::S32:
+ *dest_size_r = src_size;
+ return (const int32_t *)src;
+
+ case SampleFormat::FLOAT:
+ return pcm_allocate_float_to_32(buffer,
+ (const float *)src, src_size,
+ dest_size_r);
+ }
+
+ return NULL;
+}
+
+template<typename S, unsigned bits=DefaultSampleBits<S>::value>
+static void
+ConvertToFloat(float *dest, S src, S end)
+{
+ constexpr float factor = 0.5 / (1 << (bits - 2));
+ while (src != end)
+ *dest++ = float(*src++) * factor;
+
+}
+
+template<typename S, unsigned bits=DefaultSampleBits<S>::value>
+static void
+ConvertToFloat(float *dest, S src, size_t size)
+{
+ ConvertToFloat<S, bits>(dest, src, pcm_end_pointer(src, size));
+}
+
+template<typename S, unsigned bits=DefaultSampleBits<S>::value>
+static float *
+AllocateToFloat(PcmBuffer &buffer, S src, size_t src_size,
+ size_t *dest_size_r)
+{
+ constexpr size_t src_sample_size = sizeof(*S());
+ 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<S, bits>(dest, src, src_size);
+ return dest;
+}
+
+static float *
+pcm_allocate_8_to_float(PcmBuffer &buffer,
+ const int8_t *src, size_t src_size,
+ size_t *dest_size_r)
+{
+ return AllocateToFloat(buffer, src, src_size, dest_size_r);
+}
+
+static float *
+pcm_allocate_16_to_float(PcmBuffer &buffer,
+ const int16_t *src, size_t src_size,
+ size_t *dest_size_r)
+{
+ return AllocateToFloat(buffer, src, src_size, dest_size_r);
+}
+
+static float *
+pcm_allocate_24p32_to_float(PcmBuffer &buffer,
+ const int32_t *src, size_t src_size,
+ size_t *dest_size_r)
+{
+ return AllocateToFloat<decltype(src), 24>
+ (buffer, src, src_size, dest_size_r);
+}
+
+static float *
+pcm_allocate_32_to_float(PcmBuffer &buffer,
+ const int32_t *src, size_t src_size,
+ size_t *dest_size_r)
+{
+ return AllocateToFloat(buffer, src, src_size, dest_size_r);
+}
+
+const float *
+pcm_convert_to_float(PcmBuffer &buffer,
+ SampleFormat src_format, const void *src,
+ size_t src_size, size_t *dest_size_r)
+{
+ switch (src_format) {
+ case SampleFormat::UNDEFINED:
+ case SampleFormat::DSD:
+ break;
+
+ case SampleFormat::S8:
+ return pcm_allocate_8_to_float(buffer,
+ (const int8_t *)src, src_size,
+ dest_size_r);
+
+ 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);
+
+ case SampleFormat::S32:
+ return pcm_allocate_32_to_float(buffer,
+ (const int32_t *)src, src_size,
+ dest_size_r);
+
+ case SampleFormat::FLOAT:
+ *dest_size_r = src_size;
+ return (const float *)src;
+ }
+
+ return NULL;
+}
diff --git a/src/pcm/PcmFormat.hxx b/src/pcm/PcmFormat.hxx
new file mode 100644
index 000000000..b18b4f932
--- /dev/null
+++ b/src/pcm/PcmFormat.hxx
@@ -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.
+ */
+
+#ifndef MPD_PCM_FORMAT_HXX
+#define MPD_PCM_FORMAT_HXX
+
+#include "AudioFormat.hxx"
+
+#include <stdint.h>
+#include <stddef.h>
+
+struct PcmBuffer;
+class PcmDither;
+
+/**
+ * Converts PCM samples to 16 bit. If the source format is 24 bit,
+ * then dithering is applied.
+ *
+ * @param buffer a PcmBuffer object
+ * @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 *
+pcm_convert_to_16(PcmBuffer &buffer, PcmDither &dither,
+ SampleFormat src_format, const void *src,
+ size_t src_size, size_t *dest_size_r);
+
+/**
+ * Converts PCM samples to 24 bit (32 bit alignment).
+ *
+ * @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 *
+pcm_convert_to_24(PcmBuffer &buffer,
+ SampleFormat src_format, const void *src,
+ size_t src_size, size_t *dest_size_r);
+
+/**
+ * Converts PCM samples to 32 bit.
+ *
+ * @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 *
+pcm_convert_to_32(PcmBuffer &buffer,
+ SampleFormat src_format, const void *src,
+ size_t src_size, size_t *dest_size_r);
+
+/**
+ * Converts PCM samples to 32 bit floating point.
+ *
+ * @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 float *
+pcm_convert_to_float(PcmBuffer &buffer,
+ SampleFormat src_format, const void *src,
+ size_t src_size, size_t *dest_size_r);
+
+#endif
diff --git a/src/pcm/PcmMix.cxx b/src/pcm/PcmMix.cxx
new file mode 100644
index 000000000..f4a02fc47
--- /dev/null
+++ b/src/pcm/PcmMix.cxx
@@ -0,0 +1,209 @@
+/*
+ * 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.
+ */
+
+#include "config.h"
+#include "PcmMix.hxx"
+#include "PcmVolume.hxx"
+#include "PcmUtils.hxx"
+#include "AudioFormat.hxx"
+
+#include <math.h>
+
+template<typename T, typename U, unsigned bits>
+static T
+PcmAddVolume(T _a, T _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;
+
+ return PcmClamp<T, U, bits>(c);
+}
+
+template<typename T, typename U, unsigned bits>
+static void
+PcmAddVolume(T *a, const T *b, unsigned 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);
+}
+
+template<typename T, typename U, unsigned bits>
+static void
+PcmAddVolumeVoid(void *a, const void *b, size_t size, int volume1, int volume2)
+{
+ constexpr size_t sample_size = sizeof(T);
+ assert(size % sample_size == 0);
+
+ PcmAddVolume<T, U, bits>((T *)a, (const T *)b, size / sample_size,
+ volume1, volume2);
+}
+
+static void
+pcm_add_vol_float(float *buffer1, const float *buffer2,
+ unsigned num_samples, float volume1, float volume2)
+{
+ while (num_samples > 0) {
+ float sample1 = *buffer1;
+ float sample2 = *buffer2++;
+
+ sample1 = (sample1 * volume1 + sample2 * volume2);
+ *buffer1++ = sample1;
+ --num_samples;
+ }
+}
+
+static bool
+pcm_add_vol(void *buffer1, const void *buffer2, size_t size,
+ int vol1, int vol2,
+ SampleFormat format)
+{
+ switch (format) {
+ case SampleFormat::UNDEFINED:
+ case SampleFormat::DSD:
+ /* not implemented */
+ return false;
+
+ case SampleFormat::S8:
+ PcmAddVolumeVoid<int8_t, int32_t, 8>(buffer1, buffer2, size,
+ vol1, vol2);
+ return true;
+
+ case SampleFormat::S16:
+ PcmAddVolumeVoid<int16_t, int32_t, 16>(buffer1, buffer2, size,
+ vol1, vol2);
+ return true;
+
+ case SampleFormat::S24_P32:
+ PcmAddVolumeVoid<int32_t, int64_t, 24>(buffer1, buffer2, size,
+ vol1, vol2);
+ return true;
+
+ case SampleFormat::S32:
+ PcmAddVolumeVoid<int32_t, int64_t, 32>(buffer1, buffer2, size,
+ vol1, vol2);
+ return true;
+
+ case SampleFormat::FLOAT:
+ pcm_add_vol_float((float *)buffer1, (const float *)buffer2,
+ size / 4,
+ pcm_volume_to_float(vol1),
+ pcm_volume_to_float(vol2));
+ return true;
+ }
+
+ assert(false);
+ gcc_unreachable();
+}
+
+template<typename T, typename U, unsigned bits>
+static T
+PcmAdd(T _a, T _b)
+{
+ U a(_a), b(_b);
+ return PcmClamp<T, U, bits>(a + b);
+}
+
+template<typename T, typename U, unsigned bits>
+static void
+PcmAdd(T *a, const T *b, unsigned n)
+{
+ for (size_t i = 0; i != n; ++i)
+ a[i] = PcmAdd<T, U, bits>(a[i], b[i]);
+}
+
+template<typename T, typename U, unsigned bits>
+static void
+PcmAddVoid(void *a, const void *b, size_t size)
+{
+ constexpr size_t sample_size = sizeof(T);
+ assert(size % sample_size == 0);
+
+ PcmAdd<T, U, bits>((T *)a, (const T *)b, size / sample_size);
+}
+
+static void
+pcm_add_float(float *buffer1, const float *buffer2, unsigned num_samples)
+{
+ while (num_samples > 0) {
+ float sample1 = *buffer1;
+ float sample2 = *buffer2++;
+ *buffer1++ = sample1 + sample2;
+ --num_samples;
+ }
+}
+
+static bool
+pcm_add(void *buffer1, const void *buffer2, size_t size,
+ SampleFormat format)
+{
+ switch (format) {
+ case SampleFormat::UNDEFINED:
+ case SampleFormat::DSD:
+ /* not implemented */
+ return false;
+
+ case SampleFormat::S8:
+ PcmAddVoid<int8_t, int32_t, 8>(buffer1, buffer2, size);
+ return true;
+
+ case SampleFormat::S16:
+ PcmAddVoid<int16_t, int32_t, 16>(buffer1, buffer2, size);
+ return true;
+
+ case SampleFormat::S24_P32:
+ PcmAddVoid<int32_t, int64_t, 24>(buffer1, buffer2, size);
+ return true;
+
+ case SampleFormat::S32:
+ PcmAddVoid<int32_t, int64_t, 32>(buffer1, buffer2, size);
+ return true;
+
+ case SampleFormat::FLOAT:
+ pcm_add_float((float *)buffer1, (const float *)buffer2,
+ size / 4);
+ return true;
+ }
+
+ assert(false);
+ gcc_unreachable();
+}
+
+bool
+pcm_mix(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 NaN
+ * to signal mixing rather than fading */
+ if (isnan(portion1))
+ return pcm_add(buffer1, buffer2, size, format);
+
+ 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);
+
+ return pcm_add_vol(buffer1, buffer2, size, vol1, PCM_VOLUME_1 - vol1, format);
+}
diff --git a/src/pcm/PcmMix.hxx b/src/pcm/PcmMix.hxx
new file mode 100644
index 000000000..b50a163fd
--- /dev/null
+++ b/src/pcm/PcmMix.hxx
@@ -0,0 +1,49 @@
+/*
+ * 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_MIX_HXX
+#define MPD_PCM_MIX_HXX
+
+#include "AudioFormat.hxx"
+#include "gcc.h"
+
+#include <stddef.h>
+
+/*
+ * Linearly mixes two PCM buffers. Both must have the same length and
+ * the same audio format. The formula is:
+ *
+ * s1 := s1 * portion1 + s2 * (1 - portion1)
+ *
+ * @param buffer1 the first PCM buffer, and the destination buffer
+ * @param buffer2 the second PCM buffer
+ * @param size the size of both buffers in bytes
+ * @param format the sample format of both buffers
+ * @param portion1 a number between 0.0 and 1.0 specifying the portion
+ * of the first buffer in the mix; portion2 = (1.0 - portion1). The value
+ * NaN is used by the MixRamp code to specify that simple addition is required.
+ *
+ * @return true on success, false if the format is not supported
+ */
+gcc_warn_unused_result
+bool
+pcm_mix(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
new file mode 100644
index 000000000..0c823250d
--- /dev/null
+++ b/src/pcm/PcmPrng.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_PCM_PRNG_HXX
+#define MPD_PCM_PRNG_HXX
+
+/**
+ * A very simple linear congruential PRNG. It's good enough for PCM
+ * dithering.
+ */
+static unsigned long
+pcm_prng(unsigned long state)
+{
+ return (state * 0x0019660dL + 0x3c6ef35fL) & 0xffffffffL;
+}
+
+#endif
diff --git a/src/pcm/PcmResample.cxx b/src/pcm/PcmResample.cxx
new file mode 100644
index 000000000..423e3d442
--- /dev/null
+++ b/src/pcm/PcmResample.cxx
@@ -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.
+ */
+
+#include "config.h"
+#include "PcmResampleInternal.hxx"
+
+#ifdef HAVE_LIBSAMPLERATE
+#include "conf.h"
+#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(GError **error_r)
+{
+#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_r);
+ else
+ return true;
+#else
+ (void)error_r;
+ 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,
+ GError **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,
+ GError **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,
+ GError **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);
+}
diff --git a/src/pcm/PcmResample.hxx b/src/pcm/PcmResample.hxx
new file mode 100644
index 000000000..8699597c1
--- /dev/null
+++ b/src/pcm/PcmResample.hxx
@@ -0,0 +1,137 @@
+/*
+ * 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
+
+/**
+ * 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,
+ GError **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,
+ GError **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,
+ GError **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,
+ GError **error_r)
+ {
+ /* reuse the 32 bit code - the resampler code doesn't care if
+ the upper 8 bits are actually used */
+ return Resample32(channels, src_rate, src_buffer, src_size,
+ dest_rate, dest_size_r, error_r);
+ }
+};
+
+bool
+pcm_resample_global_init(GError **error_r);
+
+#endif
diff --git a/src/pcm/PcmResampleFallback.cxx b/src/pcm/PcmResampleFallback.cxx
new file mode 100644
index 000000000..a62cd64f7
--- /dev/null
+++ b/src/pcm/PcmResampleFallback.cxx
@@ -0,0 +1,106 @@
+/*
+ * 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
new file mode 100644
index 000000000..4ea96daea
--- /dev/null
+++ b/src/pcm/PcmResampleInternal.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.
+ */
+
+/** \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, GError **error_r);
+
+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,
+ GError **error_r);
+
+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,
+ GError **error_r);
+
+const int32_t *
+pcm_resample_lsr_32(PcmResampler *state,
+ unsigned channels,
+ unsigned src_rate,
+ const int32_t *src_buffer,
+ G_GNUC_UNUSED size_t src_size,
+ unsigned dest_rate, size_t *dest_size_r,
+ GError **error_r);
+
+#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,
+ G_GNUC_UNUSED 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
new file mode 100644
index 000000000..d8dde62e9
--- /dev/null
+++ b/src/pcm/PcmResampleLibsamplerate.cxx
@@ -0,0 +1,290 @@
+/*
+ * 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 "conf.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "pcm"
+
+static int lsr_converter = SRC_SINC_FASTEST;
+
+static inline GQuark
+libsamplerate_quark(void)
+{
+ return g_quark_from_static_string("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 (g_ascii_strncasecmp(s, name, length) == 0) {
+ lsr_converter = i;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+pcm_resample_lsr_global_init(const char *converter, GError **error_r)
+{
+ if (!lsr_parse_converter(converter)) {
+ g_set_error(error_r, libsamplerate_quark(), 0,
+ "unknown samplerate converter '%s'", converter);
+ return false;
+ }
+
+ g_debug("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,
+ GError **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) {
+ g_set_error(error_r, libsamplerate_quark(), state->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;
+ g_debug("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, GError **error_r)
+{
+ if (state->error == 0)
+ state->error = src_process(state->state, &state->data);
+ if (state->error) {
+ g_set_error(error_r, libsamplerate_quark(), 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,
+ GError **error_r)
+{
+ SRC_DATA *data = &state->data;
+
+ assert((src_size % (sizeof(*src_buffer) * channels)) == 0);
+
+ if (!pcm_resample_set(state, channels, src_rate, dest_rate, error_r))
+ 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_r))
+ 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,
+ GError **error_r)
+{
+ SRC_DATA *data = &state->data;
+
+ assert((src_size % (sizeof(*src_buffer) * channels)) == 0);
+
+ if (!pcm_resample_set(state, channels, src_rate, dest_rate,
+ error_r))
+ 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_r))
+ 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,
+ GError **error_r)
+{
+ SRC_DATA *data = &state->data;
+
+ assert((src_size % (sizeof(*src_buffer) * channels)) == 0);
+
+ if (!pcm_resample_set(state, channels, src_rate, dest_rate,
+ error_r))
+ 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_r))
+ 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;
+}
diff --git a/src/pcm/PcmUtils.hxx b/src/pcm/PcmUtils.hxx
new file mode 100644
index 000000000..d77c4194a
--- /dev/null
+++ b/src/pcm/PcmUtils.hxx
@@ -0,0 +1,66 @@
+/*
+ * 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_PCM_UTILS_H
+#define MPD_PCM_UTILS_H
+
+#include "gcc.h"
+
+#include <limits>
+
+#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);
+}
+
+/**
+ * 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>
+gcc_const
+static inline T
+PcmClamp(U x)
+{
+ constexpr U MIN_VALUE = -(U(1) << (bits - 1));
+ constexpr U MAX_VALUE = (U(1) << (bits - 1)) - 1;
+
+ typedef std::numeric_limits<T> limits;
+ static_assert(MIN_VALUE >= limits::min(), "out of range");
+ static_assert(MAX_VALUE <= limits::max(), "out of range");
+
+ if (gcc_unlikely(x < MIN_VALUE))
+ return T(MIN_VALUE);
+
+ if (gcc_unlikely(x > MAX_VALUE))
+ return T(MAX_VALUE);
+
+ return T(x);
+}
+
+#endif
diff --git a/src/pcm/PcmVolume.cxx b/src/pcm/PcmVolume.cxx
new file mode 100644
index 000000000..2a8027400
--- /dev/null
+++ b/src/pcm/PcmVolume.cxx
@@ -0,0 +1,193 @@
+/*
+ * 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.
+ */
+
+#include "config.h"
+#include "PcmVolume.hxx"
+#include "PcmUtils.hxx"
+#include "AudioFormat.hxx"
+
+#include <glib.h>
+
+#include <stdint.h>
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "pcm_volume"
+
+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, G_GNUC_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
new file mode 100644
index 000000000..8cd82acf7
--- /dev/null
+++ b/src/pcm/PcmVolume.hxx
@@ -0,0 +1,81 @@
+/*
+ * 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/dsd2pcm/dsd2pcm.c b/src/pcm/dsd2pcm/dsd2pcm.c
index 4c7640853..4c7640853 100644
--- a/src/dsd2pcm/dsd2pcm.c
+++ b/src/pcm/dsd2pcm/dsd2pcm.c
diff --git a/src/dsd2pcm/dsd2pcm.h b/src/pcm/dsd2pcm/dsd2pcm.h
index 80e8ce0cc..80e8ce0cc 100644
--- a/src/dsd2pcm/dsd2pcm.h
+++ b/src/pcm/dsd2pcm/dsd2pcm.h
diff --git a/src/pcm/dsd2pcm/dsd2pcm.hpp b/src/pcm/dsd2pcm/dsd2pcm.hpp
new file mode 100644
index 000000000..8f3f55197
--- /dev/null
+++ b/src/pcm/dsd2pcm/dsd2pcm.hpp
@@ -0,0 +1,39 @@
+#ifndef DSD2PCM_HXX_INCLUDED
+#define DSD2PCM_HXX_INCLUDED
+
+#include <algorithm>
+#include <stdexcept>
+#include "dsd2pcm.h"
+
+/**
+ * C++ PImpl Wrapper for the dsd2pcm C library
+ */
+
+class dxd
+{
+ dsd2pcm_ctx *handle;
+public:
+ dxd() : handle(dsd2pcm_init()) {}
+
+ dxd(dxd const& x) : handle(dsd2pcm_clone(x.handle)) {}
+
+ ~dxd() { dsd2pcm_destroy(handle); }
+
+ friend void swap(dxd & a, dxd & b)
+ { std::swap(a.handle,b.handle); }
+
+ dxd& operator=(dxd x)
+ { swap(*this,x); return *this; }
+
+ void translate(size_t samples,
+ const unsigned char *src, ptrdiff_t src_stride,
+ bool lsbitfirst,
+ float *dst, ptrdiff_t dst_stride)
+ {
+ dsd2pcm_translate(handle,samples,src,src_stride,
+ lsbitfirst,dst,dst_stride);
+ }
+};
+
+#endif // DSD2PCM_HXX_INCLUDED
+
diff --git a/src/dsd2pcm/info.txt b/src/pcm/dsd2pcm/info.txt
index 15ff29245..15ff29245 100644
--- a/src/dsd2pcm/info.txt
+++ b/src/pcm/dsd2pcm/info.txt
diff --git a/src/dsd2pcm/main.cpp b/src/pcm/dsd2pcm/main.cpp
index 0b58888a8..0b58888a8 100644
--- a/src/dsd2pcm/main.cpp
+++ b/src/pcm/dsd2pcm/main.cpp
diff --git a/src/dsd2pcm/noiseshape.c b/src/pcm/dsd2pcm/noiseshape.c
index ecd2f251d..ecd2f251d 100644
--- a/src/dsd2pcm/noiseshape.c
+++ b/src/pcm/dsd2pcm/noiseshape.c
diff --git a/src/dsd2pcm/noiseshape.h b/src/pcm/dsd2pcm/noiseshape.h
index 6075f0d88..6075f0d88 100644
--- a/src/dsd2pcm/noiseshape.h
+++ b/src/pcm/dsd2pcm/noiseshape.h
diff --git a/src/pcm/dsd2pcm/noiseshape.hpp b/src/pcm/dsd2pcm/noiseshape.hpp
new file mode 100644
index 000000000..1fc698b36
--- /dev/null
+++ b/src/pcm/dsd2pcm/noiseshape.hpp
@@ -0,0 +1,43 @@
+#ifndef NOISE_SHAPE_HXX_INCLUDED
+#define NOISE_SHAPE_HXX_INCLUDED
+
+#include <stdexcept>
+#include "noiseshape.h"
+
+/**
+ * C++ wrapper for the noiseshape C library
+ */
+
+class noise_shaper
+{
+ noise_shape_ctx ctx;
+public:
+ noise_shaper(int sos_count, const float *bbaa)
+ {
+ noise_shape_init(&ctx, sos_count, bbaa);
+ }
+
+ noise_shaper(noise_shaper const& x)
+ {
+ noise_shape_clone(&x.ctx,&ctx);
+ }
+
+ ~noise_shaper()
+ { noise_shape_destroy(&ctx); }
+
+ noise_shaper& operator=(noise_shaper const& x)
+ {
+ if (this != &x) {
+ noise_shape_destroy(&ctx);
+ noise_shape_clone(&x.ctx,&ctx);
+ }
+ return *this;
+ }
+
+ float get() { return noise_shape_get(&ctx); }
+
+ void update(float error) { noise_shape_update(&ctx,error); }
+};
+
+#endif /* NOISE_SHAPE_HXX_INCLUDED */
+
diff --git a/src/pcm_pack.c b/src/pcm/pcm_pack.c
index 921d880c0..921d880c0 100644
--- a/src/pcm_pack.c
+++ b/src/pcm/pcm_pack.c
diff --git a/src/pcm_pack.h b/src/pcm/pcm_pack.h
index f3184b403..f3184b403 100644
--- a/src/pcm_pack.h
+++ b/src/pcm/pcm_pack.h
diff --git a/src/pcm_buffer.c b/src/pcm_buffer.c
deleted file mode 100644
index 4b1eb875a..000000000
--- a/src/pcm_buffer.c
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "pcm_buffer.h"
-#include "poison.h"
-
-/**
- * Align the specified size to the next 8k boundary.
- */
-G_GNUC_CONST
-static size_t
-align_8k(size_t size)
-{
- return ((size - 1) | 0x1fff) + 1;
-}
-
-void *
-pcm_buffer_get(struct pcm_buffer *buffer, size_t size)
-{
- assert(buffer != NULL);
-
- if (size == 0)
- /* never return NULL, because NULL would be assumed to
- be an error condition */
- size = 1;
-
- if (buffer->size < size) {
- /* free the old buffer */
- g_free(buffer->buffer);
-
- buffer->size = align_8k(size);
- buffer->buffer = g_malloc(buffer->size);
- } else {
- /* discard old buffer contents */
- poison_undefined(buffer->buffer, buffer->size);
- }
-
- assert(buffer->size >= size);
-
- return buffer->buffer;
-}
diff --git a/src/pcm_buffer.h b/src/pcm_buffer.h
deleted file mode 100644
index 4502976f6..000000000
--- a/src/pcm_buffer.h
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * 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 PCM_BUFFER_H
-#define PCM_BUFFER_H
-
-#include "check.h"
-
-#include <glib.h>
-
-#include <assert.h>
-
-/**
- * Manager for a temporary buffer which grows as needed. We could
- * allocate a new buffer every time pcm_convert() is called, but that
- * would put too much stress on the allocator.
- */
-struct pcm_buffer {
- void *buffer;
-
- size_t size;
-};
-
-/**
- * Initialize the buffer, but don't allocate anything yet.
- */
-static inline void
-pcm_buffer_init(struct pcm_buffer *buffer)
-{
- assert(buffer != NULL);
-
- buffer->buffer = NULL;
- buffer->size = 0;
-}
-
-/**
- * Free resources. This function may be called more than once.
- */
-static inline void
-pcm_buffer_deinit(struct pcm_buffer *buffer)
-{
- assert(buffer != NULL);
-
- g_free(buffer->buffer);
-
- buffer->buffer = NULL;
-}
-
-/**
- * Get the buffer, and guarantee a minimum size. This buffer becomes
- * invalid with the next pcm_buffer_get() call.
- *
- * This function will never return NULL, even if size is zero, because
- * the PCM library uses the NULL return value to signal "error". An
- * empty destination buffer is not always an error.
- */
-G_GNUC_MALLOC
-void *
-pcm_buffer_get(struct pcm_buffer *buffer, size_t size);
-
-#endif
diff --git a/src/pcm_channels.c b/src/pcm_channels.c
deleted file mode 100644
index ec2bd69a5..000000000
--- a/src/pcm_channels.c
+++ /dev/null
@@ -1,246 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "pcm_channels.h"
-#include "pcm_buffer.h"
-#include "pcm_utils.h"
-
-#include <assert.h>
-
-static void
-pcm_convert_channels_16_1_to_2(int16_t *restrict dest,
- const int16_t *restrict src,
- const int16_t *restrict src_end)
-{
- while (src < src_end) {
- int16_t value = *src++;
-
- *dest++ = value;
- *dest++ = value;
- }
-}
-
-static void
-pcm_convert_channels_16_2_to_1(int16_t *restrict dest,
- const int16_t *restrict src,
- const int16_t *restrict src_end)
-{
- while (src < src_end) {
- int32_t a = *src++, b = *src++;
-
- *dest++ = (a + b) / 2;
- }
-}
-
-static void
-pcm_convert_channels_16_n_to_2(int16_t *restrict dest,
- unsigned src_channels,
- const int16_t *restrict src,
- const int16_t *restrict src_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;
-
- /* XXX this is actually only mono ... */
- *dest++ = value;
- *dest++ = value;
- }
-}
-
-const int16_t *
-pcm_convert_channels_16(struct pcm_buffer *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 = pcm_buffer_get(buffer, dest_size);
- const int16_t *src_end = pcm_end_pointer(src, src_size);
-
- if (src_channels == 1 && dest_channels == 2)
- pcm_convert_channels_16_1_to_2(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 NULL;
-
- return dest;
-}
-
-static void
-pcm_convert_channels_24_1_to_2(int32_t *restrict dest,
- const int32_t *restrict src,
- const int32_t *restrict src_end)
-{
- while (src < src_end) {
- int32_t value = *src++;
-
- *dest++ = value;
- *dest++ = value;
- }
-}
-
-static void
-pcm_convert_channels_24_2_to_1(int32_t *restrict dest,
- const int32_t *restrict src,
- const int32_t *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 *restrict dest,
- unsigned src_channels,
- const int32_t *restrict src,
- const int32_t *restrict src_end)
-{
- unsigned c;
-
- assert(src_channels > 0);
-
- while (src < src_end) {
- int32_t sum = 0;
- int32_t value;
-
- for (c = 0; c < src_channels; ++c)
- sum += *src++;
- value = sum / (int)src_channels;
-
- /* XXX this is actually only mono ... */
- *dest++ = value;
- *dest++ = value;
- }
-}
-
-const int32_t *
-pcm_convert_channels_24(struct pcm_buffer *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 = pcm_buffer_get(buffer, dest_size);
- const int32_t *src_end = pcm_end_pointer(src, src_size);
-
- if (src_channels == 1 && dest_channels == 2)
- pcm_convert_channels_24_1_to_2(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 NULL;
-
- return dest;
-}
-
-static void
-pcm_convert_channels_32_1_to_2(int32_t *dest, const int32_t *src,
- const int32_t *src_end)
-{
- pcm_convert_channels_24_1_to_2(dest, src, src_end);
-}
-
-static void
-pcm_convert_channels_32_2_to_1(int32_t *restrict dest,
- const int32_t *restrict src,
- const int32_t *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)
-{
- unsigned c;
-
- assert(src_channels > 0);
-
- while (src < src_end) {
- int64_t sum = 0;
- int32_t value;
-
- for (c = 0; c < src_channels; ++c)
- sum += *src++;
- value = sum / (int64_t)src_channels;
-
- /* XXX this is actually only mono ... */
- *dest++ = value;
- *dest++ = value;
- }
-}
-
-const int32_t *
-pcm_convert_channels_32(struct pcm_buffer *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 = pcm_buffer_get(buffer, dest_size);
- const int32_t *src_end = pcm_end_pointer(src, src_size);
-
- if (src_channels == 1 && dest_channels == 2)
- pcm_convert_channels_32_1_to_2(dest, src, src_end);
- else if (src_channels == 2 && dest_channels == 1)
- pcm_convert_channels_32_2_to_1(dest, src, src_end);
- else if (dest_channels == 2)
- pcm_convert_channels_32_n_to_2(dest, src_channels, src,
- src_end);
- else
- return NULL;
-
- return dest;
-}
diff --git a/src/pcm_channels.h b/src/pcm_channels.h
deleted file mode 100644
index 1e4a0991f..000000000
--- a/src/pcm_channels.h
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * 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_PCM_CHANNELS_H
-#define MPD_PCM_CHANNELS_H
-
-#include <stdint.h>
-#include <stddef.h>
-
-struct pcm_buffer;
-
-/**
- * Changes the number of channels in 16 bit PCM data.
- *
- * @param buffer the destination pcm_buffer object
- * @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 *
-pcm_convert_channels_16(struct pcm_buffer *buffer,
- unsigned dest_channels,
- unsigned src_channels, const int16_t *src,
- size_t src_size, size_t *dest_size_r);
-
-/**
- * Changes the number of channels in 24 bit PCM data (aligned at 32
- * bit boundaries).
- *
- * @param buffer the destination pcm_buffer object
- * @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 *
-pcm_convert_channels_24(struct pcm_buffer *buffer,
- unsigned dest_channels,
- unsigned src_channels, const int32_t *src,
- size_t src_size, size_t *dest_size_r);
-
-/**
- * Changes the number of channels in 32 bit PCM data.
- *
- * @param buffer the destination pcm_buffer object
- * @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 *
-pcm_convert_channels_32(struct pcm_buffer *buffer,
- unsigned dest_channels,
- unsigned src_channels, const int32_t *src,
- size_t src_size, size_t *dest_size_r);
-
-#endif
diff --git a/src/pcm_convert.c b/src/pcm_convert.c
deleted file mode 100644
index 63f9a1b98..000000000
--- a/src/pcm_convert.c
+++ /dev/null
@@ -1,379 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "pcm_convert.h"
-#include "pcm_channels.h"
-#include "pcm_format.h"
-#include "pcm_pack.h"
-#include "audio_format.h"
-#include "glib_compat.h"
-
-#include <assert.h>
-#include <string.h>
-#include <math.h>
-#include <glib.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "pcm"
-
-void pcm_convert_init(struct pcm_convert_state *state)
-{
- memset(state, 0, sizeof(*state));
-
- pcm_dsd_init(&state->dsd);
- pcm_resample_init(&state->resample);
- pcm_dither_24_init(&state->dither);
-
- pcm_buffer_init(&state->format_buffer);
- pcm_buffer_init(&state->channels_buffer);
-}
-
-void pcm_convert_deinit(struct pcm_convert_state *state)
-{
- pcm_dsd_deinit(&state->dsd);
- pcm_resample_deinit(&state->resample);
-
- pcm_buffer_deinit(&state->format_buffer);
- pcm_buffer_deinit(&state->channels_buffer);
-}
-
-void
-pcm_convert_reset(struct pcm_convert_state *state)
-{
- pcm_dsd_reset(&state->dsd);
- pcm_resample_reset(&state->resample);
-}
-
-static const void *
-pcm_convert_channels(struct pcm_buffer *buffer, enum sample_format format,
- uint8_t dest_channels,
- uint8_t src_channels, const void *src,
- size_t src_size, size_t *dest_size_r,
- GError **error_r)
-{
- const void *dest = NULL;
-
- switch (format) {
- case SAMPLE_FORMAT_UNDEFINED:
- case SAMPLE_FORMAT_S8:
- case SAMPLE_FORMAT_FLOAT:
- case SAMPLE_FORMAT_DSD:
- g_set_error(error_r, pcm_convert_quark(), 0,
- "Channel conversion not implemented for format '%s'",
- sample_format_to_string(format));
- return NULL;
-
- case SAMPLE_FORMAT_S16:
- dest = pcm_convert_channels_16(buffer, dest_channels,
- src_channels, src,
- src_size, dest_size_r);
- break;
-
- case SAMPLE_FORMAT_S24_P32:
- dest = pcm_convert_channels_24(buffer, dest_channels,
- src_channels, src,
- src_size, dest_size_r);
- break;
-
- case SAMPLE_FORMAT_S32:
- dest = pcm_convert_channels_32(buffer, dest_channels,
- src_channels, src,
- src_size, dest_size_r);
- break;
- }
-
- if (dest == NULL) {
- g_set_error(error_r, pcm_convert_quark(), 0,
- "Conversion from %u to %u channels "
- "is not implemented",
- src_channels, dest_channels);
- return NULL;
- }
-
- return dest;
-}
-
-static const int16_t *
-pcm_convert_16(struct pcm_convert_state *state,
- const struct audio_format *src_format,
- const void *src_buffer, size_t src_size,
- const struct audio_format *dest_format, size_t *dest_size_r,
- GError **error_r)
-{
- const int16_t *buf;
- size_t len;
-
- assert(dest_format->format == SAMPLE_FORMAT_S16);
-
- buf = pcm_convert_to_16(&state->format_buffer, &state->dither,
- src_format->format, src_buffer, src_size,
- &len);
- if (buf == NULL) {
- g_set_error(error_r, pcm_convert_quark(), 0,
- "Conversion from %s to 16 bit is not implemented",
- sample_format_to_string(src_format->format));
- return NULL;
- }
-
- if (src_format->channels != dest_format->channels) {
- buf = pcm_convert_channels_16(&state->channels_buffer,
- dest_format->channels,
- src_format->channels,
- buf, len, &len);
- if (buf == NULL) {
- g_set_error(error_r, pcm_convert_quark(), 0,
- "Conversion from %u to %u channels "
- "is not implemented",
- src_format->channels,
- dest_format->channels);
- return NULL;
- }
- }
-
- if (src_format->sample_rate != dest_format->sample_rate) {
- buf = pcm_resample_16(&state->resample,
- dest_format->channels,
- src_format->sample_rate, buf, len,
- dest_format->sample_rate, &len,
- error_r);
- if (buf == NULL)
- return NULL;
- }
-
- *dest_size_r = len;
- return buf;
-}
-
-static const int32_t *
-pcm_convert_24(struct pcm_convert_state *state,
- const struct audio_format *src_format,
- const void *src_buffer, size_t src_size,
- const struct audio_format *dest_format, size_t *dest_size_r,
- GError **error_r)
-{
- const int32_t *buf;
- size_t len;
-
- assert(dest_format->format == SAMPLE_FORMAT_S24_P32);
-
- buf = pcm_convert_to_24(&state->format_buffer, src_format->format,
- src_buffer, src_size, &len);
- if (buf == NULL) {
- g_set_error(error_r, pcm_convert_quark(), 0,
- "Conversion from %s to 24 bit is not implemented",
- sample_format_to_string(src_format->format));
- return NULL;
- }
-
- if (src_format->channels != dest_format->channels) {
- buf = pcm_convert_channels_24(&state->channels_buffer,
- dest_format->channels,
- src_format->channels,
- buf, len, &len);
- if (buf == NULL) {
- g_set_error(error_r, pcm_convert_quark(), 0,
- "Conversion from %u to %u channels "
- "is not implemented",
- src_format->channels,
- dest_format->channels);
- return NULL;
- }
- }
-
- if (src_format->sample_rate != dest_format->sample_rate) {
- buf = pcm_resample_24(&state->resample,
- dest_format->channels,
- src_format->sample_rate, buf, len,
- dest_format->sample_rate, &len,
- error_r);
- if (buf == NULL)
- return NULL;
- }
-
- *dest_size_r = len;
- return buf;
-}
-
-static const int32_t *
-pcm_convert_32(struct pcm_convert_state *state,
- const struct audio_format *src_format,
- const void *src_buffer, size_t src_size,
- const struct audio_format *dest_format, size_t *dest_size_r,
- GError **error_r)
-{
- const int32_t *buf;
- size_t len;
-
- assert(dest_format->format == SAMPLE_FORMAT_S32);
-
- buf = pcm_convert_to_32(&state->format_buffer, src_format->format,
- src_buffer, src_size, &len);
- if (buf == NULL) {
- g_set_error(error_r, pcm_convert_quark(), 0,
- "Conversion from %s to 32 bit is not implemented",
- sample_format_to_string(src_format->format));
- return NULL;
- }
-
- if (src_format->channels != dest_format->channels) {
- buf = pcm_convert_channels_32(&state->channels_buffer,
- dest_format->channels,
- src_format->channels,
- buf, len, &len);
- if (buf == NULL) {
- g_set_error(error_r, pcm_convert_quark(), 0,
- "Conversion from %u to %u channels "
- "is not implemented",
- src_format->channels,
- dest_format->channels);
- return NULL;
- }
- }
-
- if (src_format->sample_rate != dest_format->sample_rate) {
- buf = pcm_resample_32(&state->resample,
- dest_format->channels,
- src_format->sample_rate, buf, len,
- dest_format->sample_rate, &len,
- error_r);
- if (buf == NULL)
- return buf;
- }
-
- *dest_size_r = len;
- return buf;
-}
-
-static const float *
-pcm_convert_float(struct pcm_convert_state *state,
- const struct audio_format *src_format,
- const void *src_buffer, size_t src_size,
- const struct audio_format *dest_format, size_t *dest_size_r,
- GError **error_r)
-{
- const float *buffer = src_buffer;
- size_t size = src_size;
-
- assert(dest_format->format == SAMPLE_FORMAT_FLOAT);
-
- /* convert channels first, hoping the source format is
- supported (float is not) */
-
- if (dest_format->channels != src_format->channels) {
- buffer = pcm_convert_channels(&state->channels_buffer,
- src_format->format,
- dest_format->channels,
- src_format->channels,
- buffer, size, &size, error_r);
- if (buffer == NULL)
- return NULL;
- }
-
- /* convert to float now */
-
- buffer = pcm_convert_to_float(&state->format_buffer,
- src_format->format,
- buffer, size, &size);
- if (buffer == NULL) {
- g_set_error(error_r, pcm_convert_quark(), 0,
- "Conversion from %s to float is not implemented",
- sample_format_to_string(src_format->format));
- return NULL;
- }
-
- /* resample with float, because this is the best format for
- libsamplerate */
-
- if (src_format->sample_rate != dest_format->sample_rate) {
- buffer = pcm_resample_float(&state->resample,
- dest_format->channels,
- src_format->sample_rate,
- buffer, size,
- dest_format->sample_rate, &size,
- error_r);
- if (buffer == NULL)
- return NULL;
- }
-
- *dest_size_r = size;
- return buffer;
-}
-
-const void *
-pcm_convert(struct pcm_convert_state *state,
- const struct audio_format *src_format,
- const void *src, size_t src_size,
- const struct audio_format *dest_format,
- size_t *dest_size_r,
- GError **error_r)
-{
- struct audio_format float_format;
- if (src_format->format == SAMPLE_FORMAT_DSD) {
- size_t f_size;
- const float *f = pcm_dsd_to_float(&state->dsd,
- src_format->channels,
- false, src, src_size,
- &f_size);
- if (f == NULL) {
- g_set_error_literal(error_r, pcm_convert_quark(), 0,
- "DSD to PCM conversion failed");
- return NULL;
- }
-
- float_format = *src_format;
- float_format.format = SAMPLE_FORMAT_FLOAT;
-
- src_format = &float_format;
- src = f;
- src_size = f_size;
- }
-
- switch (dest_format->format) {
- case SAMPLE_FORMAT_S16:
- return pcm_convert_16(state,
- src_format, src, src_size,
- dest_format, dest_size_r,
- error_r);
-
- case SAMPLE_FORMAT_S24_P32:
- return pcm_convert_24(state,
- src_format, src, src_size,
- dest_format, dest_size_r,
- error_r);
-
- case SAMPLE_FORMAT_S32:
- return pcm_convert_32(state,
- src_format, src, src_size,
- dest_format, dest_size_r,
- error_r);
-
- case SAMPLE_FORMAT_FLOAT:
- return pcm_convert_float(state,
- src_format, src, src_size,
- dest_format, dest_size_r,
- error_r);
-
- default:
- g_set_error(error_r, pcm_convert_quark(), 0,
- "PCM conversion to %s is not implemented",
- sample_format_to_string(dest_format->format));
- return NULL;
- }
-}
diff --git a/src/pcm_convert.h b/src/pcm_convert.h
deleted file mode 100644
index be11a6e41..000000000
--- a/src/pcm_convert.h
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * 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 PCM_CONVERT_H
-#define PCM_CONVERT_H
-
-#include "pcm_dsd.h"
-#include "pcm_resample.h"
-#include "pcm_dither.h"
-#include "pcm_buffer.h"
-
-struct audio_format;
-
-/**
- * This object is statically allocated (within another struct), and
- * holds buffer allocations and the state for all kinds of PCM
- * conversions.
- */
-struct pcm_convert_state {
- struct pcm_dsd dsd;
-
- struct pcm_resample_state resample;
-
- struct pcm_dither dither;
-
- /** the buffer for converting the sample format */
- struct pcm_buffer format_buffer;
-
- /** the buffer for converting the channel count */
- struct pcm_buffer channels_buffer;
-};
-
-static inline GQuark
-pcm_convert_quark(void)
-{
- return g_quark_from_static_string("pcm_convert");
-}
-
-/**
- * Initializes a pcm_convert_state object.
- */
-void pcm_convert_init(struct pcm_convert_state *state);
-
-/**
- * Deinitializes a pcm_convert_state object and frees allocated
- * memory.
- */
-void pcm_convert_deinit(struct pcm_convert_state *state);
-
-/**
- * Reset the pcm_convert_state object. Use this at the boundary
- * between two distinct songs and each time the format changes.
- */
-void
-pcm_convert_reset(struct pcm_convert_state *state);
-
-/**
- * Converts PCM data between two audio formats.
- *
- * @param state an initialized pcm_convert_state object
- * @param src_format the source audio format
- * @param src the source PCM buffer
- * @param src_size the size of #src in bytes
- * @param dest_format the requested destination audio format
- * @param dest_size_r returns the number of bytes of the destination buffer
- * @param error_r location to store the error occurring, or NULL to
- * ignore errors
- * @return the destination buffer, or NULL on error
- */
-const void *
-pcm_convert(struct pcm_convert_state *state,
- const struct audio_format *src_format,
- const void *src, size_t src_size,
- const struct audio_format *dest_format,
- size_t *dest_size_r,
- GError **error_r);
-
-#endif
diff --git a/src/pcm_dither.c b/src/pcm_dither.c
deleted file mode 100644
index 4811946c8..000000000
--- a/src/pcm_dither.c
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "pcm_dither.h"
-#include "pcm_prng.h"
-
-static int16_t
-pcm_dither_sample_24_to_16(int32_t sample, struct pcm_dither *dither)
-{
- int32_t output, rnd;
-
- enum {
- from_bits = 24,
- to_bits = 16,
- scale_bits = from_bits - to_bits,
- round = 1 << (scale_bits - 1),
- mask = (1 << scale_bits) - 1,
- ONE = 1 << (from_bits - 1),
- MIN = -ONE,
- MAX = ONE - 1
- };
-
- sample += dither->error[0] - dither->error[1] + dither->error[2];
-
- dither->error[2] = dither->error[1];
- dither->error[1] = dither->error[0] / 2;
-
- /* round */
- output = sample + round;
-
- rnd = pcm_prng(dither->random);
- output += (rnd & mask) - (dither->random & mask);
-
- dither->random = rnd;
-
- /* clip */
- if (output > MAX) {
- output = MAX;
-
- if (sample > MAX)
- sample = MAX;
- } else if (output < MIN) {
- output = MIN;
-
- if (sample < MIN)
- sample = MIN;
- }
-
- output &= ~mask;
-
- dither->error[0] = sample - output;
-
- return (int16_t)(output >> scale_bits);
-}
-
-void
-pcm_dither_24_to_16(struct pcm_dither *dither,
- int16_t *dest, const int32_t *src, const int32_t *src_end)
-{
- while (src < src_end)
- *dest++ = pcm_dither_sample_24_to_16(*src++, dither);
-}
-
-static int16_t
-pcm_dither_sample_32_to_16(int32_t sample, struct pcm_dither *dither)
-{
- return pcm_dither_sample_24_to_16(sample >> 8, dither);
-}
-
-void
-pcm_dither_32_to_16(struct pcm_dither *dither,
- int16_t *dest, const int32_t *src, const int32_t *src_end)
-{
- while (src < src_end)
- *dest++ = pcm_dither_sample_32_to_16(*src++, dither);
-}
diff --git a/src/pcm_dither.h b/src/pcm_dither.h
deleted file mode 100644
index 046dea21e..000000000
--- a/src/pcm_dither.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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_PCM_DITHER_H
-#define MPD_PCM_DITHER_H
-
-#include <stdint.h>
-
-struct pcm_dither {
- int32_t error[3];
- int32_t random;
-};
-
-static inline void
-pcm_dither_24_init(struct pcm_dither *dither)
-{
- dither->error[0] = dither->error[1] = dither->error[2] = 0;
- dither->random = 0;
-}
-
-void
-pcm_dither_24_to_16(struct pcm_dither *dither,
- int16_t *dest, const int32_t *src, const int32_t *src_end);
-
-void
-pcm_dither_32_to_16(struct pcm_dither *dither,
- int16_t *dest, const int32_t *src, const int32_t *src_end);
-
-#endif
diff --git a/src/pcm_dsd.c b/src/pcm_dsd.c
deleted file mode 100644
index 76266b4cc..000000000
--- a/src/pcm_dsd.c
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * 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 "pcm_dsd.h"
-#include "dsd2pcm/dsd2pcm.h"
-
-#include <glib.h>
-#include <string.h>
-
-void
-pcm_dsd_init(struct pcm_dsd *dsd)
-{
- pcm_buffer_init(&dsd->buffer);
-
- memset(dsd->dsd2pcm, 0, sizeof(dsd->dsd2pcm));
-}
-
-void
-pcm_dsd_deinit(struct pcm_dsd *dsd)
-{
- pcm_buffer_deinit(&dsd->buffer);
-
- for (unsigned i = 0; i < G_N_ELEMENTS(dsd->dsd2pcm); ++i)
- if (dsd->dsd2pcm[i] != NULL)
- dsd2pcm_destroy(dsd->dsd2pcm[i]);
-}
-
-void
-pcm_dsd_reset(struct pcm_dsd *dsd)
-{
- for (unsigned i = 0; i < G_N_ELEMENTS(dsd->dsd2pcm); ++i)
- if (dsd->dsd2pcm[i] != NULL)
- dsd2pcm_reset(dsd->dsd2pcm[i]);
-}
-
-const float *
-pcm_dsd_to_float(struct pcm_dsd *dsd, unsigned channels, bool lsbfirst,
- const uint8_t *src, size_t src_size,
- size_t *dest_size_r)
-{
- assert(dsd != NULL);
- assert(src != NULL);
- assert(src_size > 0);
- assert(src_size % channels == 0);
- assert(channels <= G_N_ELEMENTS(dsd->dsd2pcm));
-
- 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 = pcm_buffer_get(&dsd->buffer, dest_size);
-
- for (unsigned c = 0; c < channels; ++c) {
- if (dsd->dsd2pcm[c] == NULL) {
- dsd->dsd2pcm[c] = dsd2pcm_init();
- if (dsd->dsd2pcm[c] == NULL)
- return NULL;
- }
-
- dsd2pcm_translate(dsd->dsd2pcm[c], num_frames,
- src + c, channels,
- lsbfirst, dest + c, channels);
- }
-
- return dest;
-}
diff --git a/src/pcm_dsd.h b/src/pcm_dsd.h
deleted file mode 100644
index 85c2455aa..000000000
--- a/src/pcm_dsd.h
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef MPD_PCM_DSD_H
-#define MPD_PCM_DSD_H
-
-#include "check.h"
-#include "pcm_buffer.h"
-
-#include <stdbool.h>
-#include <stdint.h>
-
-/**
- * Wrapper for the dsd2pcm library.
- */
-struct pcm_dsd {
- struct pcm_buffer buffer;
-
- struct dsd2pcm_ctx_s *dsd2pcm[32];
-};
-
-void
-pcm_dsd_init(struct pcm_dsd *dsd);
-
-void
-pcm_dsd_deinit(struct pcm_dsd *dsd);
-
-void
-pcm_dsd_reset(struct pcm_dsd *dsd);
-
-const float *
-pcm_dsd_to_float(struct pcm_dsd *dsd, unsigned channels, bool lsbfirst,
- const uint8_t *src, size_t src_size,
- size_t *dest_size_r);
-
-#endif
diff --git a/src/pcm_dsd_usb.c b/src/pcm_dsd_usb.c
deleted file mode 100644
index 4b5e39f39..000000000
--- a/src/pcm_dsd_usb.c
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * 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 "pcm_dsd_usb.h"
-#include "pcm_buffer.h"
-#include "audio_format.h"
-
-G_GNUC_CONST
-static inline uint32_t
-pcm_two_dsd_to_usb_marker1(uint8_t a, uint8_t b)
-{
- return 0xff050000 | (a << 8) | b;
-}
-
-G_GNUC_CONST
-static inline uint32_t
-pcm_two_dsd_to_usb_marker2(uint8_t a, uint8_t b)
-{
- return 0xfffa0000 | (a << 8) | b;
-}
-
-
-const uint32_t *
-pcm_dsd_to_usb(struct pcm_buffer *buffer, unsigned channels,
- const uint8_t *src, size_t src_size,
- size_t *dest_size_r)
-{
- assert(buffer != NULL);
- assert(audio_valid_channel_count(channels));
- assert(src != NULL);
- assert(src_size > 0);
- assert(src_size % channels == 0);
-
- const unsigned num_src_samples = src_size;
- const unsigned num_src_frames = num_src_samples / channels;
-
- /* this rounds down and discards the last odd frame; not
- elegant, but good enough for now */
- const unsigned num_frames = num_src_frames / 2;
- const unsigned num_samples = num_frames * channels;
-
- const size_t dest_size = num_samples * 4;
- *dest_size_r = dest_size;
- uint32_t *const dest0 = pcm_buffer_get(buffer, dest_size),
- *dest = dest0;
-
- for (unsigned i = num_frames / 2; i > 0; --i) {
- for (unsigned c = channels; c > 0; --c) {
- /* each 24 bit sample has 16 DSD sample bits
- plus the magic 0x05 marker */
-
- *dest++ = pcm_two_dsd_to_usb_marker1(src[0], src[channels]);
-
- /* seek the source pointer to the next
- channel */
- ++src;
- }
-
- /* skip the second byte of each channel, because we
- have already copied it */
- src += channels;
-
- for (unsigned c = channels; c > 0; --c) {
- /* each 24 bit sample has 16 DSD sample bits
- plus the magic 0xfa marker */
-
- *dest++ = pcm_two_dsd_to_usb_marker2(src[0], src[channels]);
-
- /* seek the source pointer to the next
- channel */
- ++src;
- }
-
- /* skip the second byte of each channel, because we
- have already copied it */
- src += channels;
- }
-
- return dest0;
-}
diff --git a/src/pcm_dsd_usb.h b/src/pcm_dsd_usb.h
deleted file mode 100644
index 389358459..000000000
--- a/src/pcm_dsd_usb.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef MPD_PCM_DSD_USB_H
-#define MPD_PCM_DSD_USB_H
-
-#include "check.h"
-
-#include <stdbool.h>
-#include <stdint.h>
-#include <stddef.h>
-
-struct pcm_buffer;
-
-/**
- * Pack DSD 1 bit samples into (padded) 24 bit PCM samples for
- * playback over USB, according to the proposed standard by
- * dCS and others:
- * http://www.sonore.us/DoP_openStandard_1v1.pdf
- */
-const uint32_t *
-pcm_dsd_to_usb(struct pcm_buffer *buffer, unsigned channels,
- const uint8_t *src, size_t src_size,
- size_t *dest_size_r);
-
-#endif
diff --git a/src/pcm_export.c b/src/pcm_export.c
deleted file mode 100644
index 144ac71cd..000000000
--- a/src/pcm_export.c
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * 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 "pcm_export.h"
-#include "pcm_dsd_usb.h"
-#include "pcm_pack.h"
-#include "util/byte_reverse.h"
-
-void
-pcm_export_init(struct pcm_export_state *state)
-{
- pcm_buffer_init(&state->reverse_buffer);
- pcm_buffer_init(&state->pack_buffer);
- pcm_buffer_init(&state->dsd_buffer);
-}
-
-void pcm_export_deinit(struct pcm_export_state *state)
-{
- pcm_buffer_deinit(&state->reverse_buffer);
- pcm_buffer_deinit(&state->pack_buffer);
- pcm_buffer_deinit(&state->dsd_buffer);
-}
-
-void
-pcm_export_open(struct pcm_export_state *state,
- enum sample_format sample_format, unsigned channels,
- bool dsd_usb, bool shift8, bool pack, bool reverse_endian)
-{
- assert(audio_valid_sample_format(sample_format));
- assert(!dsd_usb || audio_valid_channel_count(channels));
-
- state->channels = channels;
- state->dsd_usb = dsd_usb && sample_format == SAMPLE_FORMAT_DSD;
- if (state->dsd_usb)
- /* after the conversion to DSD-over-USB, the DSD
- samples are stuffed inside fake 24 bit samples */
- sample_format = SAMPLE_FORMAT_S24_P32;
-
- state->shift8 = shift8 && sample_format == SAMPLE_FORMAT_S24_P32;
- state->pack24 = pack && sample_format == SAMPLE_FORMAT_S24_P32;
-
- assert(!state->shift8 || !state->pack24);
-
- state->reverse_endian = 0;
- if (reverse_endian) {
- size_t sample_size = state->pack24
- ? 3
- : sample_format_size(sample_format);
- assert(sample_size <= 0xff);
-
- if (sample_size > 1)
- state->reverse_endian = sample_size;
- }
-}
-
-size_t
-pcm_export_frame_size(const struct pcm_export_state *state,
- const struct audio_format *audio_format)
-{
- assert(state != NULL);
- assert(audio_format != NULL);
-
- if (state->pack24)
- /* packed 24 bit samples (3 bytes per sample) */
- return audio_format->channels * 3;
-
- if (state->dsd_usb)
- /* the DSD-over-USB draft says that DSD 1-bit samples
- are enclosed within 24 bit samples, and MPD's
- representation of 24 bit is padded to 32 bit (4
- bytes per sample) */
- return audio_format->channels * 4;
-
- return audio_format_frame_size(audio_format);
-}
-
-const void *
-pcm_export(struct pcm_export_state *state, const void *data, size_t size,
- size_t *dest_size_r)
-{
- if (state->dsd_usb)
- data = pcm_dsd_to_usb(&state->dsd_buffer, state->channels,
- data, size, &size);
-
- if (state->pack24) {
- assert(size % 4 == 0);
-
- const size_t num_samples = size / 4;
- const size_t dest_size = num_samples * 3;
-
- const uint8_t *src8 = data, *src_end8 = src8 + size;
- uint8_t *dest = pcm_buffer_get(&state->pack_buffer, dest_size);
- assert(dest != NULL);
-
- pcm_pack_24(dest, (const int32_t *)src8,
- (const int32_t *)src_end8);
-
- data = dest;
- size = dest_size;
- } else if (state->shift8) {
- assert(size % 4 == 0);
-
- const uint8_t *src8 = data, *src_end8 = src8 + size;
- const uint32_t *src = (const uint32_t *)src8;
- const uint32_t *const src_end = (const uint32_t *)src_end8;
-
- uint32_t *dest = pcm_buffer_get(&state->pack_buffer, size);
- data = dest;
-
- while (src < src_end)
- *dest++ = *src++ << 8;
- }
-
-
- if (state->reverse_endian > 0) {
- assert(state->reverse_endian >= 2);
-
- void *dest = pcm_buffer_get(&state->reverse_buffer, size);
- assert(dest != NULL);
-
- const uint8_t *src = data, *src_end = src + size;
- reverse_bytes(dest, src, src_end, state->reverse_endian);
-
- data = dest;
- }
-
- *dest_size_r = size;
- return data;
-}
-
-size_t
-pcm_export_source_size(const struct pcm_export_state *state, size_t size)
-{
- if (state->pack24)
- /* 32 bit to 24 bit conversion (4 to 3 bytes) */
- size = (size / 3) * 4;
-
- if (state->dsd_usb)
- /* DSD over USB doubles the transport size */
- size /= 2;
-
- return size;
-}
diff --git a/src/pcm_export.h b/src/pcm_export.h
deleted file mode 100644
index a7e7c3f68..000000000
--- a/src/pcm_export.h
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef PCM_EXPORT_H
-#define PCM_EXPORT_H
-
-#include "check.h"
-#include "pcm_buffer.h"
-#include "audio_format.h"
-
-#include <stdbool.h>
-
-struct audio_format;
-
-/**
- * An object that handles export of PCM samples to some instance
- * outside of MPD. It has a few more options to tweak the binary
- * representation which are not supported by the pcm_convert library.
- */
-struct pcm_export_state {
- /**
- * The buffer is used to convert DSD samples to the
- * DSD-over-USB format.
- *
- * @see #dsd_usb
- */
- struct pcm_buffer dsd_buffer;
-
- /**
- * The buffer is used to pack samples, removing padding.
- *
- * @see #pack24
- */
- struct pcm_buffer pack_buffer;
-
- /**
- * The buffer is used to reverse the byte order.
- *
- * @see #reverse_endian
- */
- struct pcm_buffer reverse_buffer;
-
- /**
- * The number of channels.
- */
- uint8_t channels;
-
- /**
- * Convert DSD to DSD-over-USB? Input format must be
- * SAMPLE_FORMAT_DSD and output format must be
- * SAMPLE_FORMAT_S24_P32.
- */
- bool dsd_usb;
-
- /**
- * Convert (padded) 24 bit samples to 32 bit by shifting 8
- * bits to the left?
- */
- bool shift8;
-
- /**
- * Pack 24 bit samples?
- */
- bool pack24;
-
- /**
- * Export the samples in reverse byte order? A non-zero value
- * means the option is enabled and represents the size of each
- * sample (2 or bigger).
- */
- uint8_t reverse_endian;
-};
-
-/**
- * Initialize a #pcm_export_state object.
- */
-void
-pcm_export_init(struct pcm_export_state *state);
-
-/**
- * Deinitialize a #pcm_export_state object and free allocated memory.
- */
-void
-pcm_export_deinit(struct pcm_export_state *state);
-
-/**
- * Open the #pcm_export_state object.
- *
- * There is no "close" method. This function may be called multiple
- * times to reuse the object, until pcm_export_deinit() is called.
- *
- * This function cannot fail.
- *
- * @param channels the number of channels; ignored unless dsd_usb is set
- */
-void
-pcm_export_open(struct pcm_export_state *state,
- enum sample_format sample_format, unsigned channels,
- bool dsd_usb, bool shift8, bool pack, bool reverse_endian);
-
-/**
- * Calculate the size of one output frame.
- */
-G_GNUC_PURE
-size_t
-pcm_export_frame_size(const struct pcm_export_state *state,
- const struct audio_format *audio_format);
-
-/**
- * Export a PCM buffer.
- *
- * @param state an initialized and open pcm_export_state object
- * @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 (may be a pointer to the source buffer)
- */
-const void *
-pcm_export(struct pcm_export_state *state, const void *src, size_t src_size,
- size_t *dest_size_r);
-
-/**
- * Converts the number of consumed bytes from the pcm_export()
- * destination buffer to the according number of bytes from the
- * pcm_export() source buffer.
- */
-G_GNUC_PURE
-size_t
-pcm_export_source_size(const struct pcm_export_state *state, size_t dest_size);
-
-#endif
diff --git a/src/pcm_format.c b/src/pcm_format.c
deleted file mode 100644
index d3ea3acb0..000000000
--- a/src/pcm_format.c
+++ /dev/null
@@ -1,487 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "pcm_format.h"
-#include "pcm_dither.h"
-#include "pcm_buffer.h"
-#include "pcm_pack.h"
-#include "pcm_utils.h"
-
-static void
-pcm_convert_8_to_16(int16_t *out, const int8_t *in, const int8_t *in_end)
-{
- while (in < in_end) {
- *out++ = *in++ << 8;
- }
-}
-
-static void
-pcm_convert_24_to_16(struct pcm_dither *dither,
- int16_t *out, const int32_t *in, const int32_t *in_end)
-{
- pcm_dither_24_to_16(dither, out, in, in_end);
-}
-
-static void
-pcm_convert_32_to_16(struct pcm_dither *dither,
- int16_t *out, const int32_t *in, const int32_t *in_end)
-{
- pcm_dither_32_to_16(dither, out, in, in_end);
-}
-
-static void
-pcm_convert_float_to_16(int16_t *out, const float *in, const float *in_end)
-{
- const unsigned OUT_BITS = 16;
- const float factor = 1 << (OUT_BITS - 1);
-
- while (in < in_end) {
- int sample = *in++ * factor;
- *out++ = pcm_clamp_16(sample);
- }
-}
-
-static int16_t *
-pcm_allocate_8_to_16(struct pcm_buffer *buffer,
- const int8_t *src, size_t src_size, size_t *dest_size_r)
-{
- int16_t *dest;
- *dest_size_r = src_size / sizeof(*src) * sizeof(*dest);
- dest = pcm_buffer_get(buffer, *dest_size_r);
- pcm_convert_8_to_16(dest, src, pcm_end_pointer(src, src_size));
- return dest;
-}
-
-static int16_t *
-pcm_allocate_24p32_to_16(struct pcm_buffer *buffer, struct pcm_dither *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 = pcm_buffer_get(buffer, *dest_size_r);
- pcm_convert_24_to_16(dither, dest, src,
- pcm_end_pointer(src, src_size));
- return dest;
-}
-
-static int16_t *
-pcm_allocate_32_to_16(struct pcm_buffer *buffer, struct pcm_dither *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 = pcm_buffer_get(buffer, *dest_size_r);
- pcm_convert_32_to_16(dither, dest, src,
- pcm_end_pointer(src, src_size));
- return dest;
-}
-
-static int16_t *
-pcm_allocate_float_to_16(struct pcm_buffer *buffer,
- const float *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 = pcm_buffer_get(buffer, *dest_size_r);
- pcm_convert_float_to_16(dest, src,
- pcm_end_pointer(src, src_size));
- return dest;
-}
-
-const int16_t *
-pcm_convert_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither,
- enum sample_format src_format, const void *src,
- size_t src_size, size_t *dest_size_r)
-{
- assert(src_size % sample_format_size(src_format) == 0);
-
- switch (src_format) {
- case SAMPLE_FORMAT_UNDEFINED:
- case SAMPLE_FORMAT_DSD:
- break;
-
- case SAMPLE_FORMAT_S8:
- return pcm_allocate_8_to_16(buffer,
- src, src_size, dest_size_r);
-
- case SAMPLE_FORMAT_S16:
- *dest_size_r = src_size;
- return src;
-
- case SAMPLE_FORMAT_S24_P32:
- return pcm_allocate_24p32_to_16(buffer, dither, src, src_size,
- dest_size_r);
-
- case SAMPLE_FORMAT_S32:
- return pcm_allocate_32_to_16(buffer, dither, src, src_size,
- dest_size_r);
-
- case SAMPLE_FORMAT_FLOAT:
- return pcm_allocate_float_to_16(buffer, src, src_size,
- dest_size_r);
- }
-
- return NULL;
-}
-
-static void
-pcm_convert_8_to_24(int32_t *out, const int8_t *in, const int8_t *in_end)
-{
- while (in < in_end)
- *out++ = *in++ << 16;
-}
-
-static void
-pcm_convert_16_to_24(int32_t *out, const int16_t *in, const int16_t *in_end)
-{
- while (in < in_end)
- *out++ = *in++ << 8;
-}
-
-static void
-pcm_convert_32_to_24(int32_t *restrict out,
- const int32_t *restrict in,
- const int32_t *restrict in_end)
-{
- while (in < in_end)
- *out++ = *in++ >> 8;
-}
-
-static void
-pcm_convert_float_to_24(int32_t *out, const float *in, const float *in_end)
-{
- const unsigned OUT_BITS = 24;
- const float factor = 1 << (OUT_BITS - 1);
-
- while (in < in_end) {
- int sample = *in++ * factor;
- *out++ = pcm_clamp_24(sample);
- }
-}
-
-static int32_t *
-pcm_allocate_8_to_24(struct pcm_buffer *buffer,
- const int8_t *src, size_t src_size, size_t *dest_size_r)
-{
- int32_t *dest;
- *dest_size_r = src_size / sizeof(*src) * sizeof(*dest);
- dest = pcm_buffer_get(buffer, *dest_size_r);
- pcm_convert_8_to_24(dest, src, pcm_end_pointer(src, src_size));
- return dest;
-}
-
-static int32_t *
-pcm_allocate_16_to_24(struct pcm_buffer *buffer,
- const int16_t *src, size_t src_size, size_t *dest_size_r)
-{
- int32_t *dest;
- *dest_size_r = src_size * 2;
- assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest));
- dest = pcm_buffer_get(buffer, *dest_size_r);
- pcm_convert_16_to_24(dest, src, pcm_end_pointer(src, src_size));
- return dest;
-}
-
-static int32_t *
-pcm_allocate_32_to_24(struct pcm_buffer *buffer,
- const int32_t *src, size_t src_size, size_t *dest_size_r)
-{
- *dest_size_r = src_size;
- int32_t *dest = pcm_buffer_get(buffer, *dest_size_r);
- pcm_convert_32_to_24(dest, src, pcm_end_pointer(src, src_size));
- return dest;
-}
-
-static int32_t *
-pcm_allocate_float_to_24(struct pcm_buffer *buffer,
- const float *src, size_t src_size,
- size_t *dest_size_r)
-{
- *dest_size_r = src_size;
- int32_t *dest = pcm_buffer_get(buffer, *dest_size_r);
- pcm_convert_float_to_24(dest, src, pcm_end_pointer(src, src_size));
- return dest;
-}
-
-const int32_t *
-pcm_convert_to_24(struct pcm_buffer *buffer,
- enum sample_format src_format, const void *src,
- size_t src_size, size_t *dest_size_r)
-{
- assert(src_size % sample_format_size(src_format) == 0);
-
- switch (src_format) {
- case SAMPLE_FORMAT_UNDEFINED:
- case SAMPLE_FORMAT_DSD:
- break;
-
- case SAMPLE_FORMAT_S8:
- return pcm_allocate_8_to_24(buffer,
- src, src_size, dest_size_r);
-
- case SAMPLE_FORMAT_S16:
- return pcm_allocate_16_to_24(buffer,
- src, src_size, dest_size_r);
-
- case SAMPLE_FORMAT_S24_P32:
- *dest_size_r = src_size;
- return src;
-
- case SAMPLE_FORMAT_S32:
- return pcm_allocate_32_to_24(buffer, src, src_size,
- dest_size_r);
-
- case SAMPLE_FORMAT_FLOAT:
- return pcm_allocate_float_to_24(buffer, src, src_size,
- dest_size_r);
- }
-
- return NULL;
-}
-
-static void
-pcm_convert_8_to_32(int32_t *out, const int8_t *in, const int8_t *in_end)
-{
- while (in < in_end)
- *out++ = *in++ << 24;
-}
-
-static void
-pcm_convert_16_to_32(int32_t *out, const int16_t *in, const int16_t *in_end)
-{
- while (in < in_end)
- *out++ = *in++ << 16;
-}
-
-static void
-pcm_convert_24_to_32(int32_t *restrict out,
- const int32_t *restrict in,
- const int32_t *restrict in_end)
-{
- while (in < in_end)
- *out++ = *in++ << 8;
-}
-
-static int32_t *
-pcm_allocate_8_to_32(struct pcm_buffer *buffer,
- const int8_t *src, size_t src_size, size_t *dest_size_r)
-{
- int32_t *dest;
- *dest_size_r = src_size / sizeof(*src) * sizeof(*dest);
- dest = pcm_buffer_get(buffer, *dest_size_r);
- pcm_convert_8_to_32(dest, src, pcm_end_pointer(src, src_size));
- return dest;
-}
-
-static int32_t *
-pcm_allocate_16_to_32(struct pcm_buffer *buffer,
- const int16_t *src, size_t src_size, size_t *dest_size_r)
-{
- int32_t *dest;
- *dest_size_r = src_size * 2;
- assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest));
- dest = pcm_buffer_get(buffer, *dest_size_r);
- pcm_convert_16_to_32(dest, src, pcm_end_pointer(src, src_size));
- return dest;
-}
-
-static int32_t *
-pcm_allocate_24p32_to_32(struct pcm_buffer *buffer,
- const int32_t *src, size_t src_size,
- size_t *dest_size_r)
-{
- *dest_size_r = src_size;
- int32_t *dest = pcm_buffer_get(buffer, *dest_size_r);
- pcm_convert_24_to_32(dest, src, pcm_end_pointer(src, src_size));
- return dest;
-}
-
-static int32_t *
-pcm_allocate_float_to_32(struct pcm_buffer *buffer,
- const float *src, size_t src_size,
- size_t *dest_size_r)
-{
- /* convert to S24_P32 first */
- int32_t *dest = pcm_allocate_float_to_24(buffer, src, src_size,
- dest_size_r);
-
- /* convert to 32 bit in-place */
- pcm_convert_24_to_32(dest, dest, pcm_end_pointer(dest, *dest_size_r));
- return dest;
-}
-
-const int32_t *
-pcm_convert_to_32(struct pcm_buffer *buffer,
- enum sample_format src_format, const void *src,
- size_t src_size, size_t *dest_size_r)
-{
- assert(src_size % sample_format_size(src_format) == 0);
-
- switch (src_format) {
- case SAMPLE_FORMAT_UNDEFINED:
- case SAMPLE_FORMAT_DSD:
- break;
-
- case SAMPLE_FORMAT_S8:
- return pcm_allocate_8_to_32(buffer, src, src_size,
- dest_size_r);
-
- case SAMPLE_FORMAT_S16:
- return pcm_allocate_16_to_32(buffer, src, src_size,
- dest_size_r);
-
- case SAMPLE_FORMAT_S24_P32:
- return pcm_allocate_24p32_to_32(buffer, src, src_size,
- dest_size_r);
-
- case SAMPLE_FORMAT_S32:
- *dest_size_r = src_size;
- return src;
-
- case SAMPLE_FORMAT_FLOAT:
- return pcm_allocate_float_to_32(buffer, src, src_size,
- dest_size_r);
- }
-
- return NULL;
-}
-
-static void
-pcm_convert_8_to_float(float *out, const int8_t *in, const int8_t *in_end)
-{
- enum { in_bits = sizeof(*in) * 8 };
- static const float factor = 2.0f / (1 << in_bits);
- while (in < in_end)
- *out++ = (float)*in++ * factor;
-}
-
-static void
-pcm_convert_16_to_float(float *out, const int16_t *in, const int16_t *in_end)
-{
- enum { in_bits = sizeof(*in) * 8 };
- static const float factor = 2.0f / (1 << in_bits);
- while (in < in_end)
- *out++ = (float)*in++ * factor;
-}
-
-static void
-pcm_convert_24_to_float(float *out, const int32_t *in, const int32_t *in_end)
-{
- enum { in_bits = 24 };
- static const float factor = 2.0f / (1 << in_bits);
- while (in < in_end)
- *out++ = (float)*in++ * factor;
-}
-
-static void
-pcm_convert_32_to_float(float *out, const int32_t *in, const int32_t *in_end)
-{
- enum { in_bits = sizeof(*in) * 8 };
- static const float factor = 0.5f / (1 << (in_bits - 2));
- while (in < in_end)
- *out++ = (float)*in++ * factor;
-}
-
-static float *
-pcm_allocate_8_to_float(struct pcm_buffer *buffer,
- const int8_t *src, size_t src_size,
- size_t *dest_size_r)
-{
- float *dest;
- *dest_size_r = src_size / sizeof(*src) * sizeof(*dest);
- dest = pcm_buffer_get(buffer, *dest_size_r);
- pcm_convert_8_to_float(dest, src, pcm_end_pointer(src, src_size));
- return dest;
-}
-
-static float *
-pcm_allocate_16_to_float(struct pcm_buffer *buffer,
- const int16_t *src, size_t src_size,
- size_t *dest_size_r)
-{
- float *dest;
- *dest_size_r = src_size * 2;
- assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest));
- dest = pcm_buffer_get(buffer, *dest_size_r);
- pcm_convert_16_to_float(dest, src, pcm_end_pointer(src, src_size));
- return dest;
-}
-
-static float *
-pcm_allocate_24p32_to_float(struct pcm_buffer *buffer,
- const int32_t *src, size_t src_size,
- size_t *dest_size_r)
-{
- *dest_size_r = src_size;
- float *dest = pcm_buffer_get(buffer, *dest_size_r);
- pcm_convert_24_to_float(dest, src, pcm_end_pointer(src, src_size));
- return dest;
-}
-
-static float *
-pcm_allocate_32_to_float(struct pcm_buffer *buffer,
- const int32_t *src, size_t src_size,
- size_t *dest_size_r)
-{
- *dest_size_r = src_size;
- float *dest = pcm_buffer_get(buffer, *dest_size_r);
- pcm_convert_32_to_float(dest, src, pcm_end_pointer(src, src_size));
- return dest;
-}
-
-const float *
-pcm_convert_to_float(struct pcm_buffer *buffer,
- enum sample_format src_format, const void *src,
- size_t src_size, size_t *dest_size_r)
-{
- switch (src_format) {
- case SAMPLE_FORMAT_UNDEFINED:
- case SAMPLE_FORMAT_DSD:
- break;
-
- case SAMPLE_FORMAT_S8:
- return pcm_allocate_8_to_float(buffer,
- src, src_size, dest_size_r);
-
- case SAMPLE_FORMAT_S16:
- return pcm_allocate_16_to_float(buffer,
- src, src_size, dest_size_r);
-
- case SAMPLE_FORMAT_S24_P32:
- return pcm_allocate_24p32_to_float(buffer,
- src, src_size, dest_size_r);
-
- case SAMPLE_FORMAT_S32:
- return pcm_allocate_32_to_float(buffer,
- src, src_size, dest_size_r);
-
- case SAMPLE_FORMAT_FLOAT:
- *dest_size_r = src_size;
- return src;
- }
-
- return NULL;
-}
diff --git a/src/pcm_format.h b/src/pcm_format.h
deleted file mode 100644
index 48bcd0662..000000000
--- a/src/pcm_format.h
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * 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 PCM_FORMAT_H
-#define PCM_FORMAT_H
-
-#include "audio_format.h"
-
-#include <stdint.h>
-#include <stddef.h>
-
-struct pcm_buffer;
-struct pcm_dither;
-
-/**
- * Converts PCM samples to 16 bit. If the source format is 24 bit,
- * then dithering is applied.
- *
- * @param buffer a pcm_buffer object
- * @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 *
-pcm_convert_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither,
- enum sample_format src_format, const void *src,
- size_t src_size, size_t *dest_size_r);
-
-/**
- * Converts PCM samples to 24 bit (32 bit alignment).
- *
- * @param buffer a pcm_buffer 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 *
-pcm_convert_to_24(struct pcm_buffer *buffer,
- enum sample_format src_format, const void *src,
- size_t src_size, size_t *dest_size_r);
-
-/**
- * Converts PCM samples to 32 bit.
- *
- * @param buffer a pcm_buffer 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 *
-pcm_convert_to_32(struct pcm_buffer *buffer,
- enum sample_format src_format, const void *src,
- size_t src_size, size_t *dest_size_r);
-
-/**
- * Converts PCM samples to 32 bit floating point.
- *
- * @param buffer a pcm_buffer 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 float *
-pcm_convert_to_float(struct pcm_buffer *buffer,
- enum sample_format src_format, const void *src,
- size_t src_size, size_t *dest_size_r);
-
-#endif
diff --git a/src/pcm_mix.c b/src/pcm_mix.c
deleted file mode 100644
index 6c6d1b4ab..000000000
--- a/src/pcm_mix.c
+++ /dev/null
@@ -1,280 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "pcm_mix.h"
-#include "pcm_volume.h"
-#include "pcm_utils.h"
-#include "audio_format.h"
-
-#include <glib.h>
-
-#include <math.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "pcm"
-
-static void
-pcm_add_vol_8(int8_t *buffer1, const int8_t *buffer2,
- unsigned num_samples, int volume1, int volume2)
-{
- while (num_samples > 0) {
- int32_t sample1 = *buffer1;
- int32_t sample2 = *buffer2++;
-
- sample1 = ((sample1 * volume1 + sample2 * volume2) +
- pcm_volume_dither() + PCM_VOLUME_1 / 2)
- / PCM_VOLUME_1;
-
- *buffer1++ = pcm_range(sample1, 8);
- --num_samples;
- }
-}
-
-static void
-pcm_add_vol_16(int16_t *buffer1, const int16_t *buffer2,
- unsigned num_samples, int volume1, int volume2)
-{
- while (num_samples > 0) {
- int32_t sample1 = *buffer1;
- int32_t sample2 = *buffer2++;
-
- sample1 = ((sample1 * volume1 + sample2 * volume2) +
- pcm_volume_dither() + PCM_VOLUME_1 / 2)
- / PCM_VOLUME_1;
-
- *buffer1++ = pcm_range(sample1, 16);
- --num_samples;
- }
-}
-
-static void
-pcm_add_vol_24(int32_t *buffer1, const int32_t *buffer2,
- unsigned num_samples, unsigned volume1, unsigned volume2)
-{
- while (num_samples > 0) {
- int64_t sample1 = *buffer1;
- int64_t sample2 = *buffer2++;
-
- sample1 = ((sample1 * volume1 + sample2 * volume2) +
- pcm_volume_dither() + PCM_VOLUME_1 / 2)
- / PCM_VOLUME_1;
-
- *buffer1++ = pcm_range(sample1, 24);
- --num_samples;
- }
-}
-
-static void
-pcm_add_vol_32(int32_t *buffer1, const int32_t *buffer2,
- unsigned num_samples, unsigned volume1, unsigned volume2)
-{
- while (num_samples > 0) {
- int64_t sample1 = *buffer1;
- int64_t sample2 = *buffer2++;
-
- sample1 = ((sample1 * volume1 + sample2 * volume2) +
- pcm_volume_dither() + PCM_VOLUME_1 / 2)
- / PCM_VOLUME_1;
-
- *buffer1++ = pcm_range_64(sample1, 32);
- --num_samples;
- }
-}
-
-static void
-pcm_add_vol_float(float *buffer1, const float *buffer2,
- unsigned num_samples, float volume1, float volume2)
-{
- while (num_samples > 0) {
- float sample1 = *buffer1;
- float sample2 = *buffer2++;
-
- sample1 = (sample1 * volume1 + sample2 * volume2);
- *buffer1++ = sample1;
- --num_samples;
- }
-}
-
-static bool
-pcm_add_vol(void *buffer1, const void *buffer2, size_t size,
- int vol1, int vol2,
- enum sample_format format)
-{
- switch (format) {
- case SAMPLE_FORMAT_UNDEFINED:
- case SAMPLE_FORMAT_DSD:
- /* not implemented */
- return false;
-
- case SAMPLE_FORMAT_S8:
- pcm_add_vol_8((int8_t *)buffer1, (const int8_t *)buffer2,
- size, vol1, vol2);
- return true;
-
- case SAMPLE_FORMAT_S16:
- pcm_add_vol_16((int16_t *)buffer1, (const int16_t *)buffer2,
- size / 2, vol1, vol2);
- return true;
-
- case SAMPLE_FORMAT_S24_P32:
- pcm_add_vol_24((int32_t *)buffer1, (const int32_t *)buffer2,
- size / 4, vol1, vol2);
- return true;
-
- case SAMPLE_FORMAT_S32:
- pcm_add_vol_32((int32_t *)buffer1, (const int32_t *)buffer2,
- size / 4, vol1, vol2);
- return true;
-
- case SAMPLE_FORMAT_FLOAT:
- pcm_add_vol_float(buffer1, buffer2, size / 4,
- pcm_volume_to_float(vol1),
- pcm_volume_to_float(vol2));
- return true;
- }
-
- /* unreachable */
- assert(false);
- return false;
-}
-
-static void
-pcm_add_8(int8_t *buffer1, const int8_t *buffer2, unsigned num_samples)
-{
- while (num_samples > 0) {
- int32_t sample1 = *buffer1;
- int32_t sample2 = *buffer2++;
-
- sample1 += sample2;
-
- *buffer1++ = pcm_range(sample1, 8);
- --num_samples;
- }
-}
-
-static void
-pcm_add_16(int16_t *buffer1, const int16_t *buffer2, unsigned num_samples)
-{
- while (num_samples > 0) {
- int32_t sample1 = *buffer1;
- int32_t sample2 = *buffer2++;
-
- sample1 += sample2;
-
- *buffer1++ = pcm_range(sample1, 16);
- --num_samples;
- }
-}
-
-static void
-pcm_add_24(int32_t *buffer1, const int32_t *buffer2, unsigned num_samples)
-{
- while (num_samples > 0) {
- int64_t sample1 = *buffer1;
- int64_t sample2 = *buffer2++;
-
- sample1 += sample2;
-
- *buffer1++ = pcm_range(sample1, 24);
- --num_samples;
- }
-}
-
-static void
-pcm_add_32(int32_t *buffer1, const int32_t *buffer2, unsigned num_samples)
-{
- while (num_samples > 0) {
- int64_t sample1 = *buffer1;
- int64_t sample2 = *buffer2++;
-
- sample1 += sample2;
-
- *buffer1++ = pcm_range_64(sample1, 32);
- --num_samples;
- }
-}
-
-static void
-pcm_add_float(float *buffer1, const float *buffer2, unsigned num_samples)
-{
- while (num_samples > 0) {
- float sample1 = *buffer1;
- float sample2 = *buffer2++;
- *buffer1++ = sample1 + sample2;
- --num_samples;
- }
-}
-
-static bool
-pcm_add(void *buffer1, const void *buffer2, size_t size,
- enum sample_format format)
-{
- switch (format) {
- case SAMPLE_FORMAT_UNDEFINED:
- case SAMPLE_FORMAT_DSD:
- /* not implemented */
- return false;
-
- case SAMPLE_FORMAT_S8:
- pcm_add_8((int8_t *)buffer1, (const int8_t *)buffer2, size);
- return true;
-
- case SAMPLE_FORMAT_S16:
- pcm_add_16((int16_t *)buffer1, (const int16_t *)buffer2, size / 2);
- return true;
-
- case SAMPLE_FORMAT_S24_P32:
- pcm_add_24((int32_t *)buffer1, (const int32_t *)buffer2, size / 4);
- return true;
-
- case SAMPLE_FORMAT_S32:
- pcm_add_32((int32_t *)buffer1, (const int32_t *)buffer2, size / 4);
- return true;
-
- case SAMPLE_FORMAT_FLOAT:
- pcm_add_float(buffer1, buffer2, size / 4);
- return true;
- }
-
- /* unreachable */
- assert(false);
- return false;
-}
-
-bool
-pcm_mix(void *buffer1, const void *buffer2, size_t size,
- enum sample_format format, float portion1)
-{
- int vol1;
- float s;
-
- /* portion1 is between 0.0 and 1.0 for crossfading, MixRamp uses NaN
- * to signal mixing rather than fading */
- if (isnan(portion1))
- return pcm_add(buffer1, buffer2, size, format);
-
- 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);
-
- return pcm_add_vol(buffer1, buffer2, size, vol1, PCM_VOLUME_1 - vol1, format);
-}
diff --git a/src/pcm_mix.h b/src/pcm_mix.h
deleted file mode 100644
index 0e58d01ee..000000000
--- a/src/pcm_mix.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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 PCM_MIX_H
-#define PCM_MIX_H
-
-#include "audio_format.h"
-
-#include <stdbool.h>
-#include <stddef.h>
-
-/*
- * Linearly mixes two PCM buffers. Both must have the same length and
- * the same audio format. The formula is:
- *
- * s1 := s1 * portion1 + s2 * (1 - portion1)
- *
- * @param buffer1 the first PCM buffer, and the destination buffer
- * @param buffer2 the second PCM buffer
- * @param size the size of both buffers in bytes
- * @param format the sample format of both buffers
- * @param portion1 a number between 0.0 and 1.0 specifying the portion
- * of the first buffer in the mix; portion2 = (1.0 - portion1). The value
- * NaN is used by the MixRamp code to specify that simple addition is required.
- *
- * @return true on success, false if the format is not supported
- */
-G_GNUC_WARN_UNUSED_RESULT
-bool
-pcm_mix(void *buffer1, const void *buffer2, size_t size,
- enum sample_format format, float portion1);
-
-#endif
diff --git a/src/pcm_prng.h b/src/pcm_prng.h
deleted file mode 100644
index 457ba4b66..000000000
--- a/src/pcm_prng.h
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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 PCM_PRNG_H
-#define PCM_PRNG_H
-
-/**
- * A very simple linear congruential PRNG. It's good enough for PCM
- * dithering.
- */
-static unsigned long
-pcm_prng(unsigned long state)
-{
- return (state * 0x0019660dL + 0x3c6ef35fL) & 0xffffffffL;
-}
-
-#endif
diff --git a/src/pcm_resample.c b/src/pcm_resample.c
deleted file mode 100644
index 4bc057a7e..000000000
--- a/src/pcm_resample.c
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "pcm_resample_internal.h"
-
-#ifdef HAVE_LIBSAMPLERATE
-#include "conf.h"
-#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(GError **error_r)
-{
-#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_r);
- else
- return true;
-#else
- (void)error_r;
- return true;
-#endif
-}
-
-void pcm_resample_init(struct pcm_resample_state *state)
-{
-#ifdef HAVE_LIBSAMPLERATE
- if (pcm_resample_lsr_enabled())
- pcm_resample_lsr_init(state);
- else
-#endif
- pcm_resample_fallback_init(state);
-}
-
-void pcm_resample_deinit(struct pcm_resample_state *state)
-{
-#ifdef HAVE_LIBSAMPLERATE
- if (pcm_resample_lsr_enabled())
- pcm_resample_lsr_deinit(state);
- else
-#endif
- pcm_resample_fallback_deinit(state);
-}
-
-void
-pcm_resample_reset(struct pcm_resample_state *state)
-{
-#ifdef HAVE_LIBSAMPLERATE
- pcm_resample_lsr_reset(state);
-#else
- (void)state;
-#endif
-}
-
-const float *
-pcm_resample_float(struct pcm_resample_state *state,
- unsigned channels,
- unsigned src_rate,
- const float *src_buffer, size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- GError **error_r)
-{
-#ifdef HAVE_LIBSAMPLERATE
- if (pcm_resample_lsr_enabled())
- return pcm_resample_lsr_float(state, 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(state, channels,
- src_rate, (const int32_t *)src_buffer,
- src_size,
- dest_rate, dest_size_r);
-}
-
-const int16_t *
-pcm_resample_16(struct pcm_resample_state *state,
- unsigned channels,
- unsigned src_rate, const int16_t *src_buffer, size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- GError **error_r)
-{
-#ifdef HAVE_LIBSAMPLERATE
- if (pcm_resample_lsr_enabled())
- return pcm_resample_lsr_16(state, channels,
- src_rate, src_buffer, src_size,
- dest_rate, dest_size_r,
- error_r);
-#else
- (void)error_r;
-#endif
-
- return pcm_resample_fallback_16(state, channels,
- src_rate, src_buffer, src_size,
- dest_rate, dest_size_r);
-}
-
-const int32_t *
-pcm_resample_32(struct pcm_resample_state *state,
- unsigned channels,
- unsigned src_rate, const int32_t *src_buffer, size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- GError **error_r)
-{
-#ifdef HAVE_LIBSAMPLERATE
- if (pcm_resample_lsr_enabled())
- return pcm_resample_lsr_32(state, channels,
- src_rate, src_buffer, src_size,
- dest_rate, dest_size_r,
- error_r);
-#else
- (void)error_r;
-#endif
-
- return pcm_resample_fallback_32(state, channels,
- src_rate, src_buffer, src_size,
- dest_rate, dest_size_r);
-}
diff --git a/src/pcm_resample.h b/src/pcm_resample.h
deleted file mode 100644
index a49a24142..000000000
--- a/src/pcm_resample.h
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * 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_PCM_RESAMPLE_H
-#define MPD_PCM_RESAMPLE_H
-
-#include "check.h"
-#include "pcm_buffer.h"
-
-#include <stdint.h>
-#include <stddef.h>
-#include <stdbool.h>
-
-#ifdef HAVE_LIBSAMPLERATE
-#include <samplerate.h>
-#endif
-
-/**
- * This object is statically allocated (within another struct), and
- * holds buffer allocations and the state for the resampler.
- */
-struct pcm_resample_state {
-#ifdef HAVE_LIBSAMPLERATE
- SRC_STATE *state;
- SRC_DATA data;
-
- struct pcm_buffer in, out;
-
- struct {
- unsigned src_rate;
- unsigned dest_rate;
- unsigned channels;
- } prev;
-
- int error;
-#endif
-
- struct pcm_buffer buffer;
-};
-
-bool
-pcm_resample_global_init(GError **error_r);
-
-/**
- * Initializes a pcm_resample_state object.
- */
-void pcm_resample_init(struct pcm_resample_state *state);
-
-/**
- * Deinitializes a pcm_resample_state object and frees allocated
- * memory.
- */
-void pcm_resample_deinit(struct pcm_resample_state *state);
-
-/**
- * @see pcm_convert_reset()
- */
-void
-pcm_resample_reset(struct pcm_resample_state *state);
-
-/**
- * Resamples 32 bit float data.
- *
- * @param state an initialized pcm_resample_state object
- * @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 *
-pcm_resample_float(struct pcm_resample_state *state,
- unsigned channels,
- unsigned src_rate,
- const float *src_buffer, size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- GError **error_r);
-
-/**
- * Resamples 16 bit PCM data.
- *
- * @param state an initialized pcm_resample_state object
- * @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 *
-pcm_resample_16(struct pcm_resample_state *state,
- unsigned channels,
- unsigned src_rate,
- const int16_t *src_buffer, size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- GError **error_r);
-
-/**
- * Resamples 32 bit PCM data.
- *
- * @param state an initialized pcm_resample_state object
- * @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 *
-pcm_resample_32(struct pcm_resample_state *state,
- unsigned channels,
- unsigned src_rate,
- const int32_t *src_buffer, size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- GError **error_r);
-
-/**
- * Resamples 24 bit PCM data.
- *
- * @param state an initialized pcm_resample_state object
- * @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
- */
-static inline const int32_t *
-pcm_resample_24(struct pcm_resample_state *state,
- unsigned channels,
- unsigned src_rate,
- const int32_t *src_buffer, size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- GError **error_r)
-{
- /* reuse the 32 bit code - the resampler code doesn't care if
- the upper 8 bits are actually used */
- return pcm_resample_32(state, channels,
- src_rate, src_buffer, src_size,
- dest_rate, dest_size_r, error_r);
-}
-
-#endif
diff --git a/src/pcm_resample_fallback.c b/src/pcm_resample_fallback.c
deleted file mode 100644
index 1d1dfdf59..000000000
--- a/src/pcm_resample_fallback.c
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "pcm_resample_internal.h"
-
-#include <assert.h>
-
-void
-pcm_resample_fallback_init(struct pcm_resample_state *state)
-{
- pcm_buffer_init(&state->buffer);
-}
-
-void
-pcm_resample_fallback_deinit(struct pcm_resample_state *state)
-{
- pcm_buffer_deinit(&state->buffer);
-}
-
-/* resampling code blatantly ripped from ESD */
-const int16_t *
-pcm_resample_fallback_16(struct pcm_resample_state *state,
- unsigned channels,
- unsigned src_rate,
- const int16_t *src_buffer, size_t src_size,
- unsigned dest_rate,
- size_t *dest_size_r)
-{
- unsigned src_pos, 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 = pcm_buffer_get(&state->buffer, dest_size);
-
- assert((src_size % (sizeof(*src_buffer) * channels)) == 0);
-
- switch (channels) {
- case 1:
- while (dest_pos < dest_samples) {
- src_pos = dest_pos * src_rate / dest_rate;
-
- dest_buffer[dest_pos++] = src_buffer[src_pos];
- }
- break;
- case 2:
- while (dest_pos < dest_samples) {
- 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(struct pcm_resample_state *state,
- unsigned channels,
- unsigned src_rate,
- const int32_t *src_buffer, size_t src_size,
- unsigned dest_rate,
- size_t *dest_size_r)
-{
- unsigned src_pos, 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 = pcm_buffer_get(&state->buffer, dest_size);
-
- assert((src_size % (sizeof(*src_buffer) * channels)) == 0);
-
- switch (channels) {
- case 1:
- while (dest_pos < dest_samples) {
- src_pos = dest_pos * src_rate / dest_rate;
-
- dest_buffer[dest_pos++] = src_buffer[src_pos];
- }
- break;
- case 2:
- while (dest_pos < dest_samples) {
- 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_resample_internal.h b/src/pcm_resample_internal.h
deleted file mode 100644
index a0e108d4b..000000000
--- a/src/pcm_resample_internal.h
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * 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.
- */
-
-/** \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_H
-#define MPD_PCM_RESAMPLE_INTERNAL_H
-
-#include "check.h"
-#include "pcm_resample.h"
-
-#ifdef HAVE_LIBSAMPLERATE
-
-bool
-pcm_resample_lsr_global_init(const char *converter, GError **error_r);
-
-void
-pcm_resample_lsr_init(struct pcm_resample_state *state);
-
-void
-pcm_resample_lsr_deinit(struct pcm_resample_state *state);
-
-void
-pcm_resample_lsr_reset(struct pcm_resample_state *state);
-
-const float *
-pcm_resample_lsr_float(struct pcm_resample_state *state,
- unsigned channels,
- unsigned src_rate,
- const float *src_buffer, size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- GError **error_r);
-
-const int16_t *
-pcm_resample_lsr_16(struct pcm_resample_state *state,
- unsigned channels,
- unsigned src_rate,
- const int16_t *src_buffer, size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- GError **error_r);
-
-const int32_t *
-pcm_resample_lsr_32(struct pcm_resample_state *state,
- unsigned channels,
- unsigned src_rate,
- const int32_t *src_buffer,
- G_GNUC_UNUSED size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- GError **error_r);
-
-#endif
-
-void
-pcm_resample_fallback_init(struct pcm_resample_state *state);
-
-void
-pcm_resample_fallback_deinit(struct pcm_resample_state *state);
-
-const int16_t *
-pcm_resample_fallback_16(struct pcm_resample_state *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(struct pcm_resample_state *state,
- unsigned channels,
- unsigned src_rate,
- const int32_t *src_buffer,
- G_GNUC_UNUSED size_t src_size,
- unsigned dest_rate,
- size_t *dest_size_r);
-
-#endif
diff --git a/src/pcm_resample_libsamplerate.c b/src/pcm_resample_libsamplerate.c
deleted file mode 100644
index f957e5155..000000000
--- a/src/pcm_resample_libsamplerate.c
+++ /dev/null
@@ -1,312 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "pcm_resample_internal.h"
-#include "conf.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <stdlib.h>
-#include <string.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "pcm"
-
-static int lsr_converter = SRC_SINC_FASTEST;
-
-static inline GQuark
-libsamplerate_quark(void)
-{
- return g_quark_from_static_string("libsamplerate");
-}
-
-static bool
-lsr_parse_converter(const char *s)
-{
- assert(s != NULL);
-
- if (*s == 0)
- return true;
-
- char *endptr;
- long l = strtol(s, &endptr, 10);
- if (*endptr == 0 && src_get_name(l) != NULL) {
- lsr_converter = l;
- return true;
- }
-
- size_t length = strlen(s);
- for (int i = 0;; ++i) {
- const char *name = src_get_name(i);
- if (name == NULL)
- break;
-
- if (g_ascii_strncasecmp(s, name, length) == 0) {
- lsr_converter = i;
- return true;
- }
- }
-
- return false;
-}
-
-bool
-pcm_resample_lsr_global_init(const char *converter, GError **error_r)
-{
- if (!lsr_parse_converter(converter)) {
- g_set_error(error_r, libsamplerate_quark(), 0,
- "unknown samplerate converter '%s'", converter);
- return false;
- }
-
- g_debug("libsamplerate converter '%s'",
- src_get_name(lsr_converter));
-
- return true;
-}
-
-void
-pcm_resample_lsr_init(struct pcm_resample_state *state)
-{
- memset(state, 0, sizeof(*state));
-
- pcm_buffer_init(&state->in);
- pcm_buffer_init(&state->out);
- pcm_buffer_init(&state->buffer);
-}
-
-void
-pcm_resample_lsr_deinit(struct pcm_resample_state *state)
-{
- if (state->state != NULL)
- state->state = src_delete(state->state);
-
- pcm_buffer_deinit(&state->in);
- pcm_buffer_deinit(&state->out);
- pcm_buffer_deinit(&state->buffer);
-}
-
-void
-pcm_resample_lsr_reset(struct pcm_resample_state *state)
-{
- if (state->state != NULL)
- src_reset(state->state);
-}
-
-static bool
-pcm_resample_set(struct pcm_resample_state *state,
- unsigned channels, unsigned src_rate, unsigned dest_rate,
- GError **error_r)
-{
- int error;
- SRC_DATA *data = &state->data;
-
- /* (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);
-
- state->state = src_new(lsr_converter, channels, &error);
- if (!state->state) {
- g_set_error(error_r, libsamplerate_quark(), state->error,
- "libsamplerate initialization has failed: %s",
- src_strerror(error));
- return false;
- }
-
- data->src_ratio = (double)dest_rate / (double)src_rate;
- g_debug("setting samplerate conversion ratio to %.2lf",
- data->src_ratio);
- src_set_ratio(state->state, data->src_ratio);
-
- return true;
-}
-
-static bool
-lsr_process(struct pcm_resample_state *state, GError **error_r)
-{
- if (state->error == 0)
- state->error = src_process(state->state, &state->data);
- if (state->error) {
- g_set_error(error_r, libsamplerate_quark(), state->error,
- "libsamplerate has failed: %s",
- src_strerror(state->error));
- return false;
- }
-
- return true;
-}
-
-static float *
-deconst_float_buffer(const float *in)
-{
- union {
- const float *in;
- float *out;
- } u = { .in = in };
- return u.out;
-}
-
-const float *
-pcm_resample_lsr_float(struct pcm_resample_state *state,
- unsigned channels,
- unsigned src_rate,
- const float *src_buffer, size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- GError **error_r)
-{
- assert((src_size % (sizeof(*src_buffer) * channels)) == 0);
-
- if (!pcm_resample_set(state, channels, src_rate, dest_rate, error_r))
- return NULL;
-
- SRC_DATA *data = &state->data;
- data->input_frames = src_size / sizeof(*src_buffer) / channels;
- data->data_in = deconst_float_buffer(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 = pcm_buffer_get(&state->out, data_out_size);
-
- if (!lsr_process(state, error_r))
- return NULL;
-
- *dest_size_r = data->output_frames_gen *
- sizeof(*data->data_out) * channels;
- return data->data_out;
-}
-
-const int16_t *
-pcm_resample_lsr_16(struct pcm_resample_state *state,
- unsigned channels,
- unsigned src_rate,
- const int16_t *src_buffer, size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- GError **error_r)
-{
- bool success;
- SRC_DATA *data = &state->data;
- size_t data_in_size;
- size_t data_out_size;
- int16_t *dest_buffer;
-
- assert((src_size % (sizeof(*src_buffer) * channels)) == 0);
-
- success = pcm_resample_set(state, channels, src_rate, dest_rate,
- error_r);
- if (!success)
- return NULL;
-
- data->input_frames = src_size / sizeof(*src_buffer) / channels;
- data_in_size = data->input_frames * sizeof(float) * channels;
- data->data_in = pcm_buffer_get(&state->in, data_in_size);
-
- data->output_frames = (src_size * dest_rate + src_rate - 1) / src_rate;
- data_out_size = data->output_frames * sizeof(float) * channels;
- data->data_out = pcm_buffer_get(&state->out, data_out_size);
-
- src_short_to_float_array(src_buffer, data->data_in,
- data->input_frames * channels);
-
- if (!lsr_process(state, error_r))
- return NULL;
-
- *dest_size_r = data->output_frames_gen *
- sizeof(*dest_buffer) * channels;
- dest_buffer = pcm_buffer_get(&state->buffer, *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(struct pcm_resample_state *state,
- unsigned channels,
- unsigned src_rate,
- const int32_t *src_buffer, size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- GError **error_r)
-{
- bool success;
- SRC_DATA *data = &state->data;
- size_t data_in_size;
- size_t data_out_size;
- int32_t *dest_buffer;
-
- assert((src_size % (sizeof(*src_buffer) * channels)) == 0);
-
- success = pcm_resample_set(state, channels, src_rate, dest_rate,
- error_r);
- if (!success)
- return NULL;
-
- data->input_frames = src_size / sizeof(*src_buffer) / channels;
- data_in_size = data->input_frames * sizeof(float) * channels;
- data->data_in = pcm_buffer_get(&state->in, data_in_size);
-
- data->output_frames = (src_size * dest_rate + src_rate - 1) / src_rate;
- data_out_size = data->output_frames * sizeof(float) * channels;
- data->data_out = pcm_buffer_get(&state->out, data_out_size);
-
- src_int_to_float_array(src_buffer, data->data_in,
- data->input_frames * channels);
-
- if (!lsr_process(state, error_r))
- return NULL;
-
- *dest_size_r = data->output_frames_gen *
- sizeof(*dest_buffer) * channels;
- dest_buffer = pcm_buffer_get(&state->buffer, *dest_size_r);
- src_float_to_int_array(data->data_out, dest_buffer,
- data->output_frames_gen * channels);
-
- return dest_buffer;
-}
diff --git a/src/pcm_utils.h b/src/pcm_utils.h
deleted file mode 100644
index 4ad896570..000000000
--- a/src/pcm_utils.h
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * 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_PCM_UTILS_H
-#define MPD_PCM_UTILS_H
-
-#include <glib.h>
-
-#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.
- */
-static inline const void *
-pcm_end_pointer(const void *p, size_t size)
-{
- return (const char *)p + size;
-}
-
-/**
- * Check if the value is within the range of the provided bit size,
- * and caps it if necessary.
- */
-static inline int32_t
-pcm_range(int32_t sample, unsigned bits)
-{
- if (G_UNLIKELY(sample < (-1 << (bits - 1))))
- return -1 << (bits - 1);
- if (G_UNLIKELY(sample >= (1 << (bits - 1))))
- return (1 << (bits - 1)) - 1;
- return sample;
-}
-
-/**
- * Check if the value is within the range of the provided bit size,
- * and caps it if necessary.
- */
-static inline int64_t
-pcm_range_64(int64_t sample, unsigned bits)
-{
- if (G_UNLIKELY(sample < ((int64_t)-1 << (bits - 1))))
- return (int64_t)-1 << (bits - 1);
- if (G_UNLIKELY(sample >= ((int64_t)1 << (bits - 1))))
- return ((int64_t)1 << (bits - 1)) - 1;
- return sample;
-}
-
-G_GNUC_CONST
-static inline int16_t
-pcm_clamp_16(int x)
-{
- static const int32_t MIN_VALUE = G_MININT16;
- static const int32_t MAX_VALUE = G_MAXINT16;
-
- if (G_UNLIKELY(x < MIN_VALUE))
- return MIN_VALUE;
- if (G_UNLIKELY(x > MAX_VALUE))
- return MAX_VALUE;
- return x;
-}
-
-G_GNUC_CONST
-static inline int32_t
-pcm_clamp_24(int x)
-{
- static const int32_t MIN_VALUE = -(1 << 23);
- static const int32_t MAX_VALUE = (1 << 23) - 1;
-
- if (G_UNLIKELY(x < MIN_VALUE))
- return MIN_VALUE;
- if (G_UNLIKELY(x > MAX_VALUE))
- return MAX_VALUE;
- return x;
-}
-
-#endif
diff --git a/src/pcm_volume.c b/src/pcm_volume.c
deleted file mode 100644
index 49c86026f..000000000
--- a/src/pcm_volume.c
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "pcm_volume.h"
-#include "pcm_utils.h"
-#include "audio_format.h"
-
-#include <glib.h>
-
-#include <stdint.h>
-#include <string.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "pcm_volume"
-
-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++ = pcm_range(sample, 8);
- }
-}
-
-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++ = pcm_range(sample, 16);
- }
-}
-
-#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, G_GNUC_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++ = pcm_range(sample, 24);
- }
-}
-
-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++ = pcm_range_64(sample, 32);
-#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,
- enum sample_format 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 SAMPLE_FORMAT_UNDEFINED:
- case SAMPLE_FORMAT_DSD:
- /* not implemented */
- return false;
-
- case SAMPLE_FORMAT_S8:
- pcm_volume_change_8(buffer, end, volume);
- return true;
-
- case SAMPLE_FORMAT_S16:
- pcm_volume_change_16(buffer, end, volume);
- return true;
-
- case SAMPLE_FORMAT_S24_P32:
- pcm_volume_change_24(buffer, end, volume);
- return true;
-
- case SAMPLE_FORMAT_S32:
- pcm_volume_change_32(buffer, end, volume);
- return true;
-
- case SAMPLE_FORMAT_FLOAT:
- pcm_volume_change_float(buffer, end,
- pcm_volume_to_float(volume));
- return true;
- }
-
- /* unreachable */
- assert(false);
- return false;
-}
diff --git a/src/pcm_volume.h b/src/pcm_volume.h
deleted file mode 100644
index 64e3c7641..000000000
--- a/src/pcm_volume.h
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * 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 PCM_VOLUME_H
-#define PCM_VOLUME_H
-
-#include "pcm_prng.h"
-#include "audio_format.h"
-
-#include <stdint.h>
-#include <stdbool.h>
-
-enum {
- /** this value means "100% volume" */
- PCM_VOLUME_1 = 1024,
-};
-
-struct audio_format;
-
-/**
- * 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,
- enum sample_format format,
- int volume);
-
-#endif
diff --git a/src/permission.c b/src/permission.c
deleted file mode 100644
index cd52b9c86..000000000
--- a/src/permission.c
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "permission.h"
-#include "conf.h"
-#include "mpd_error.h"
-
-#include <glib.h>
-
-#include <stdbool.h>
-#include <string.h>
-
-#define PERMISSION_PASSWORD_CHAR '@'
-#define PERMISSION_SEPERATOR ","
-
-#define PERMISSION_READ_STRING "read"
-#define PERMISSION_ADD_STRING "add"
-#define PERMISSION_CONTROL_STRING "control"
-#define PERMISSION_ADMIN_STRING "admin"
-
-static GHashTable *permission_passwords;
-
-static unsigned permission_default;
-
-static unsigned parsePermissions(const char *string)
-{
- unsigned permission = 0;
- gchar **tokens;
-
- if (!string)
- return 0;
-
- tokens = g_strsplit(string, PERMISSION_SEPERATOR, 0);
- for (unsigned i = 0; tokens[i] != NULL; ++i) {
- char *temp = tokens[i];
-
- if (strcmp(temp, PERMISSION_READ_STRING) == 0) {
- permission |= PERMISSION_READ;
- } else if (strcmp(temp, PERMISSION_ADD_STRING) == 0) {
- permission |= PERMISSION_ADD;
- } else if (strcmp(temp, PERMISSION_CONTROL_STRING) == 0) {
- permission |= PERMISSION_CONTROL;
- } else if (strcmp(temp, PERMISSION_ADMIN_STRING) == 0) {
- permission |= PERMISSION_ADMIN;
- } else {
- MPD_ERROR("unknown permission \"%s\"", temp);
- }
- }
-
- g_strfreev(tokens);
-
- return permission;
-}
-
-void initPermissions(void)
-{
- char *password;
- unsigned permission;
- const struct config_param *param;
-
- permission_passwords = g_hash_table_new_full(g_str_hash, g_str_equal,
- g_free, NULL);
-
- permission_default = PERMISSION_READ | PERMISSION_ADD |
- PERMISSION_CONTROL | PERMISSION_ADMIN;
-
- param = config_get_next_param(CONF_PASSWORD, NULL);
-
- if (param) {
- permission_default = 0;
-
- do {
- const char *separator =
- strchr(param->value, PERMISSION_PASSWORD_CHAR);
-
- if (separator == NULL)
- MPD_ERROR("\"%c\" not found in password string "
- "\"%s\", line %i",
- PERMISSION_PASSWORD_CHAR,
- param->value, param->line);
-
- password = g_strndup(param->value,
- separator - param->value);
-
- permission = parsePermissions(separator + 1);
-
- g_hash_table_replace(permission_passwords,
- password,
- GINT_TO_POINTER(permission));
- } while ((param = config_get_next_param(CONF_PASSWORD, param)));
- }
-
- param = config_get_param(CONF_DEFAULT_PERMS);
-
- if (param)
- permission_default = parsePermissions(param->value);
-}
-
-int getPermissionFromPassword(char const* password, unsigned* permission)
-{
- bool found;
- gpointer key, value;
-
- found = g_hash_table_lookup_extended(permission_passwords,
- password, &key, &value);
- if (!found)
- return -1;
-
- *permission = GPOINTER_TO_INT(value);
- return 0;
-}
-
-void finishPermissions(void)
-{
- g_hash_table_destroy(permission_passwords);
-}
-
-unsigned getDefaultPermissions(void)
-{
- return permission_default;
-}
diff --git a/src/permission.h b/src/permission.h
deleted file mode 100644
index 6c3771362..000000000
--- a/src/permission.h
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * 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_PERMISSION_H
-#define MPD_PERMISSION_H
-
-#define PERMISSION_NONE 0
-#define PERMISSION_READ 1
-#define PERMISSION_ADD 2
-#define PERMISSION_CONTROL 4
-#define PERMISSION_ADMIN 8
-
-
-int getPermissionFromPassword(char const* password, unsigned* permission);
-
-void finishPermissions(void);
-
-unsigned getDefaultPermissions(void);
-
-void initPermissions(void);
-
-#endif
diff --git a/src/pipe.c b/src/pipe.c
deleted file mode 100644
index d8131432f..000000000
--- a/src/pipe.c
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "pipe.h"
-#include "buffer.h"
-#include "chunk.h"
-
-#include <glib.h>
-
-#include <assert.h>
-
-struct music_pipe {
- /** the first chunk */
- struct music_chunk *head;
-
- /** a pointer to the tail of the chunk */
- struct music_chunk **tail_r;
-
- /** the current number of chunks */
- unsigned size;
-
- /** a mutex which protects #head and #tail_r */
- GMutex *mutex;
-
-#ifndef NDEBUG
- struct audio_format audio_format;
-#endif
-};
-
-struct music_pipe *
-music_pipe_new(void)
-{
- struct music_pipe *mp = g_new(struct music_pipe, 1);
-
- mp->head = NULL;
- mp->tail_r = &mp->head;
- mp->size = 0;
- mp->mutex = g_mutex_new();
-
-#ifndef NDEBUG
- audio_format_clear(&mp->audio_format);
-#endif
-
- return mp;
-}
-
-void
-music_pipe_free(struct music_pipe *mp)
-{
- assert(mp->head == NULL);
- assert(mp->tail_r == &mp->head);
-
- g_mutex_free(mp->mutex);
- g_free(mp);
-}
-
-#ifndef NDEBUG
-
-bool
-music_pipe_check_format(const struct music_pipe *pipe,
- const struct audio_format *audio_format)
-{
- assert(pipe != NULL);
- assert(audio_format != NULL);
-
- return !audio_format_defined(&pipe->audio_format) ||
- audio_format_equals(&pipe->audio_format, audio_format);
-}
-
-bool
-music_pipe_contains(const struct music_pipe *mp,
- const struct music_chunk *chunk)
-{
- g_mutex_lock(mp->mutex);
-
- for (const struct music_chunk *i = mp->head;
- i != NULL; i = i->next) {
- if (i == chunk) {
- g_mutex_unlock(mp->mutex);
- return true;
- }
- }
-
- g_mutex_unlock(mp->mutex);
-
- return false;
-}
-
-#endif
-
-const struct music_chunk *
-music_pipe_peek(const struct music_pipe *mp)
-{
- return mp->head;
-}
-
-struct music_chunk *
-music_pipe_shift(struct music_pipe *mp)
-{
- struct music_chunk *chunk;
-
- g_mutex_lock(mp->mutex);
-
- chunk = mp->head;
- if (chunk != NULL) {
- assert(!music_chunk_is_empty(chunk));
-
- mp->head = chunk->next;
- --mp->size;
-
- if (mp->head == NULL) {
- assert(mp->size == 0);
- assert(mp->tail_r == &chunk->next);
-
- mp->tail_r = &mp->head;
- } else {
- assert(mp->size > 0);
- assert(mp->tail_r != &chunk->next);
- }
-
-#ifndef NDEBUG
- /* poison the "next" reference */
- chunk->next = (void*)0x01010101;
-
- if (mp->size == 0)
- audio_format_clear(&mp->audio_format);
-#endif
- }
-
- g_mutex_unlock(mp->mutex);
-
- return chunk;
-}
-
-void
-music_pipe_clear(struct music_pipe *mp, struct music_buffer *buffer)
-{
- struct music_chunk *chunk;
-
- while ((chunk = music_pipe_shift(mp)) != NULL)
- music_buffer_return(buffer, chunk);
-}
-
-void
-music_pipe_push(struct music_pipe *mp, struct music_chunk *chunk)
-{
- assert(!music_chunk_is_empty(chunk));
- assert(chunk->length == 0 || audio_format_valid(&chunk->audio_format));
-
- g_mutex_lock(mp->mutex);
-
- assert(mp->size > 0 || !audio_format_defined(&mp->audio_format));
- assert(!audio_format_defined(&mp->audio_format) ||
- music_chunk_check_format(chunk, &mp->audio_format));
-
-#ifndef NDEBUG
- if (!audio_format_defined(&mp->audio_format) && chunk->length > 0)
- mp->audio_format = chunk->audio_format;
-#endif
-
- chunk->next = NULL;
- *mp->tail_r = chunk;
- mp->tail_r = &chunk->next;
-
- ++mp->size;
-
- g_mutex_unlock(mp->mutex);
-}
-
-unsigned
-music_pipe_size(const struct music_pipe *mp)
-{
- g_mutex_lock(mp->mutex);
- unsigned size = mp->size;
- g_mutex_unlock(mp->mutex);
- return size;
-}
diff --git a/src/pipe.h b/src/pipe.h
deleted file mode 100644
index 84b9869e0..000000000
--- a/src/pipe.h
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * 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_PIPE_H
-#define MPD_PIPE_H
-
-#include <glib.h>
-#include <stdbool.h>
-
-#ifndef NDEBUG
-struct audio_format;
-#endif
-
-struct music_chunk;
-struct music_buffer;
-
-/**
- * A queue of #music_chunk objects. One party appends chunks at the
- * tail, and the other consumes them from the head.
- */
-struct music_pipe;
-
-/**
- * Creates a new #music_pipe object. It is empty.
- */
-G_GNUC_MALLOC
-struct music_pipe *
-music_pipe_new(void);
-
-/**
- * Frees the object. It must be empty now.
- */
-void
-music_pipe_free(struct music_pipe *mp);
-
-#ifndef NDEBUG
-
-/**
- * Checks if the audio format if the chunk is equal to the specified
- * audio_format.
- */
-bool
-music_pipe_check_format(const struct music_pipe *pipe,
- const struct audio_format *audio_format);
-
-/**
- * Checks if the specified chunk is enqueued in the music pipe.
- */
-bool
-music_pipe_contains(const struct music_pipe *mp,
- const struct music_chunk *chunk);
-
-#endif
-
-/**
- * Returns the first #music_chunk from the pipe. Returns NULL if the
- * pipe is empty.
- */
-G_GNUC_PURE
-const struct music_chunk *
-music_pipe_peek(const struct music_pipe *mp);
-
-/**
- * Removes the first chunk from the head, and returns it.
- */
-struct music_chunk *
-music_pipe_shift(struct music_pipe *mp);
-
-/**
- * Clears the whole pipe and returns the chunks to the buffer.
- *
- * @param buffer the buffer object to return the chunks to
- */
-void
-music_pipe_clear(struct music_pipe *mp, struct music_buffer *buffer);
-
-/**
- * Pushes a chunk to the tail of the pipe.
- */
-void
-music_pipe_push(struct music_pipe *mp, struct music_chunk *chunk);
-
-/**
- * Returns the number of chunks currently in this pipe.
- */
-G_GNUC_PURE
-unsigned
-music_pipe_size(const struct music_pipe *mp);
-
-G_GNUC_PURE
-static inline bool
-music_pipe_empty(const struct music_pipe *mp)
-{
- return music_pipe_size(mp) == 0;
-}
-
-#endif
diff --git a/src/player_control.c b/src/player_control.c
deleted file mode 100644
index b18639087..000000000
--- a/src/player_control.c
+++ /dev/null
@@ -1,389 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "player_control.h"
-#include "decoder_control.h"
-#include "path.h"
-#include "log.h"
-#include "tag.h"
-#include "song.h"
-#include "idle.h"
-#include "pcm_volume.h"
-#include "main.h"
-
-#include <assert.h>
-#include <stdio.h>
-#include <math.h>
-
-static void
-pc_enqueue_song_locked(struct player_control *pc, struct song *song);
-
-struct player_control *
-pc_new(unsigned buffer_chunks, unsigned int buffered_before_play)
-{
- struct player_control *pc = g_new0(struct player_control, 1);
-
- pc->buffer_chunks = buffer_chunks;
- pc->buffered_before_play = buffered_before_play;
-
- pc->mutex = g_mutex_new();
- pc->cond = g_cond_new();
-
- pc->command = PLAYER_COMMAND_NONE;
- pc->error = PLAYER_ERROR_NOERROR;
- pc->state = PLAYER_STATE_STOP;
- pc->cross_fade_seconds = 0;
- pc->mixramp_db = 0;
- pc->mixramp_delay_seconds = nanf("");
-
- return pc;
-}
-
-void
-pc_free(struct player_control *pc)
-{
- g_cond_free(pc->cond);
- g_mutex_free(pc->mutex);
- g_free(pc);
-}
-
-void
-player_wait_decoder(struct player_control *pc, struct decoder_control *dc)
-{
- assert(pc != NULL);
- assert(dc != NULL);
- assert(dc->client_cond == pc->cond);
-
- /* during this function, the decoder lock is held, because
- we're waiting for the decoder thread */
- g_cond_wait(pc->cond, dc->mutex);
-}
-
-void
-pc_song_deleted(struct player_control *pc, const struct song *song)
-{
- if (pc->errored_song == song) {
- pc->error = PLAYER_ERROR_NOERROR;
- pc->errored_song = NULL;
- }
-}
-
-static void
-player_command_wait_locked(struct player_control *pc)
-{
- while (pc->command != PLAYER_COMMAND_NONE)
- g_cond_wait(main_cond, pc->mutex);
-}
-
-static void
-player_command_locked(struct player_control *pc, enum player_command cmd)
-{
- assert(pc->command == PLAYER_COMMAND_NONE);
-
- pc->command = cmd;
- player_signal(pc);
- player_command_wait_locked(pc);
-}
-
-static void
-player_command(struct player_control *pc, enum player_command cmd)
-{
- player_lock(pc);
- player_command_locked(pc, cmd);
- player_unlock(pc);
-}
-
-void
-pc_play(struct player_control *pc, struct song *song)
-{
- assert(song != NULL);
-
- player_lock(pc);
-
- if (pc->state != PLAYER_STATE_STOP)
- player_command_locked(pc, PLAYER_COMMAND_STOP);
-
- assert(pc->next_song == NULL);
-
- pc_enqueue_song_locked(pc, song);
-
- assert(pc->next_song == NULL);
-
- player_unlock(pc);
-}
-
-void
-pc_cancel(struct player_control *pc)
-{
- player_command(pc, PLAYER_COMMAND_CANCEL);
- assert(pc->next_song == NULL);
-}
-
-void
-pc_stop(struct player_control *pc)
-{
- player_command(pc, PLAYER_COMMAND_CLOSE_AUDIO);
- assert(pc->next_song == NULL);
-
- idle_add(IDLE_PLAYER);
-}
-
-void
-pc_update_audio(struct player_control *pc)
-{
- player_command(pc, PLAYER_COMMAND_UPDATE_AUDIO);
-}
-
-void
-pc_kill(struct player_control *pc)
-{
- assert(pc->thread != NULL);
-
- player_command(pc, PLAYER_COMMAND_EXIT);
- g_thread_join(pc->thread);
- pc->thread = NULL;
-
- idle_add(IDLE_PLAYER);
-}
-
-void
-pc_pause(struct player_control *pc)
-{
- player_lock(pc);
-
- if (pc->state != PLAYER_STATE_STOP) {
- player_command_locked(pc, PLAYER_COMMAND_PAUSE);
- idle_add(IDLE_PLAYER);
- }
-
- player_unlock(pc);
-}
-
-static void
-pc_pause_locked(struct player_control *pc)
-{
- if (pc->state != PLAYER_STATE_STOP) {
- player_command_locked(pc, PLAYER_COMMAND_PAUSE);
- idle_add(IDLE_PLAYER);
- }
-}
-
-void
-pc_set_pause(struct player_control *pc, bool pause_flag)
-{
- player_lock(pc);
-
- switch (pc->state) {
- case PLAYER_STATE_STOP:
- break;
-
- case PLAYER_STATE_PLAY:
- if (pause_flag)
- pc_pause_locked(pc);
- break;
-
- case PLAYER_STATE_PAUSE:
- if (!pause_flag)
- pc_pause_locked(pc);
- break;
- }
-
- player_unlock(pc);
-}
-
-void
-pc_set_border_pause(struct player_control *pc, bool border_pause)
-{
- player_lock(pc);
- pc->border_pause = border_pause;
- player_unlock(pc);
-}
-
-void
-pc_get_status(struct player_control *pc, struct player_status *status)
-{
- player_lock(pc);
- player_command_locked(pc, PLAYER_COMMAND_REFRESH);
-
- status->state = pc->state;
-
- if (pc->state != PLAYER_STATE_STOP) {
- status->bit_rate = pc->bit_rate;
- status->audio_format = pc->audio_format;
- status->total_time = pc->total_time;
- status->elapsed_time = pc->elapsed_time;
- }
-
- player_unlock(pc);
-}
-
-enum player_state
-pc_get_state(struct player_control *pc)
-{
- return pc->state;
-}
-
-void
-pc_clear_error(struct player_control *pc)
-{
- player_lock(pc);
- pc->error = PLAYER_ERROR_NOERROR;
- pc->errored_song = NULL;
- player_unlock(pc);
-}
-
-enum player_error
-pc_get_error(struct player_control *pc)
-{
- return pc->error;
-}
-
-static char *
-pc_errored_song_uri(struct player_control *pc)
-{
- return song_get_uri(pc->errored_song);
-}
-
-char *
-pc_get_error_message(struct player_control *pc)
-{
- char *error;
- char *uri;
-
- switch (pc->error) {
- case PLAYER_ERROR_NOERROR:
- return NULL;
-
- case PLAYER_ERROR_FILENOTFOUND:
- uri = pc_errored_song_uri(pc);
- error = g_strdup_printf("file \"%s\" does not exist or is inaccessible", uri);
- g_free(uri);
- return error;
-
- case PLAYER_ERROR_FILE:
- uri = pc_errored_song_uri(pc);
- error = g_strdup_printf("problems decoding \"%s\"", uri);
- g_free(uri);
- return error;
-
- case PLAYER_ERROR_AUDIO:
- return g_strdup("problems opening audio device");
-
- case PLAYER_ERROR_SYSTEM:
- return g_strdup("system error occurred");
-
- case PLAYER_ERROR_UNKTYPE:
- uri = pc_errored_song_uri(pc);
- error = g_strdup_printf("file type of \"%s\" is unknown", uri);
- g_free(uri);
- return error;
- }
-
- assert(false);
- return NULL;
-}
-
-static void
-pc_enqueue_song_locked(struct player_control *pc, struct song *song)
-{
- assert(song != NULL);
- assert(pc->next_song == NULL);
-
- pc->next_song = song;
- player_command_locked(pc, PLAYER_COMMAND_QUEUE);
-}
-
-void
-pc_enqueue_song(struct player_control *pc, struct song *song)
-{
- assert(song != NULL);
-
- player_lock(pc);
- pc_enqueue_song_locked(pc, song);
- player_unlock(pc);
-}
-
-bool
-pc_seek(struct player_control *pc, struct song *song, float seek_time)
-{
- assert(song != NULL);
-
- player_lock(pc);
- pc->next_song = song;
- pc->seek_where = seek_time;
- player_command_locked(pc, PLAYER_COMMAND_SEEK);
- player_unlock(pc);
-
- assert(pc->next_song == NULL);
-
- idle_add(IDLE_PLAYER);
-
- return true;
-}
-
-float
-pc_get_cross_fade(const struct player_control *pc)
-{
- return pc->cross_fade_seconds;
-}
-
-void
-pc_set_cross_fade(struct player_control *pc, float cross_fade_seconds)
-{
- if (cross_fade_seconds < 0)
- cross_fade_seconds = 0;
- pc->cross_fade_seconds = cross_fade_seconds;
-
- idle_add(IDLE_OPTIONS);
-}
-
-float
-pc_get_mixramp_db(const struct player_control *pc)
-{
- return pc->mixramp_db;
-}
-
-void
-pc_set_mixramp_db(struct player_control *pc, float mixramp_db)
-{
- pc->mixramp_db = mixramp_db;
-
- idle_add(IDLE_OPTIONS);
-}
-
-float
-pc_get_mixramp_delay(const struct player_control *pc)
-{
- return pc->mixramp_delay_seconds;
-}
-
-void
-pc_set_mixramp_delay(struct player_control *pc, float mixramp_delay_seconds)
-{
- pc->mixramp_delay_seconds = mixramp_delay_seconds;
-
- idle_add(IDLE_OPTIONS);
-}
-
-double
-pc_get_total_play_time(const struct player_control *pc)
-{
- return pc->total_play_time;
-}
diff --git a/src/player_control.h b/src/player_control.h
deleted file mode 100644
index a77d31ec5..000000000
--- a/src/player_control.h
+++ /dev/null
@@ -1,287 +0,0 @@
-/*
- * 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_PLAYER_H
-#define MPD_PLAYER_H
-
-#include "audio_format.h"
-
-#include <glib.h>
-
-#include <stdint.h>
-
-struct decoder_control;
-
-enum player_state {
- PLAYER_STATE_STOP = 0,
- PLAYER_STATE_PAUSE,
- PLAYER_STATE_PLAY
-};
-
-enum player_command {
- PLAYER_COMMAND_NONE = 0,
- PLAYER_COMMAND_EXIT,
- PLAYER_COMMAND_STOP,
- PLAYER_COMMAND_PAUSE,
- PLAYER_COMMAND_SEEK,
- PLAYER_COMMAND_CLOSE_AUDIO,
-
- /**
- * At least one audio_output.enabled flag has been modified;
- * commit those changes to the output threads.
- */
- PLAYER_COMMAND_UPDATE_AUDIO,
-
- /** player_control.next_song has been updated */
- PLAYER_COMMAND_QUEUE,
-
- /**
- * cancel pre-decoding player_control.next_song; if the player
- * has already started playing this song, it will completely
- * stop
- */
- PLAYER_COMMAND_CANCEL,
-
- /**
- * Refresh status information in the #player_control struct,
- * e.g. elapsed_time.
- */
- PLAYER_COMMAND_REFRESH,
-};
-
-enum player_error {
- PLAYER_ERROR_NOERROR = 0,
- PLAYER_ERROR_FILE,
- PLAYER_ERROR_AUDIO,
- PLAYER_ERROR_SYSTEM,
- PLAYER_ERROR_UNKTYPE,
- PLAYER_ERROR_FILENOTFOUND,
-};
-
-struct player_status {
- enum player_state state;
- uint16_t bit_rate;
- struct audio_format audio_format;
- float total_time;
- float elapsed_time;
-};
-
-struct player_control {
- unsigned buffer_chunks;
-
- unsigned int buffered_before_play;
-
- /** the handle of the player thread, or NULL if the player
- thread isn't running */
- GThread *thread;
-
- /**
- * This lock protects #command, #state, #error.
- */
- GMutex *mutex;
-
- /**
- * Trigger this object after you have modified #command.
- */
- GCond *cond;
-
- enum player_command command;
- enum player_state state;
- enum player_error error;
- uint16_t bit_rate;
- struct audio_format audio_format;
- float total_time;
- float elapsed_time;
- struct song *next_song;
- const struct song *errored_song;
- double seek_where;
- float cross_fade_seconds;
- float mixramp_db;
- float mixramp_delay_seconds;
- double total_play_time;
-
- /**
- * If this flag is set, then the player will be auto-paused at
- * the end of the song, before the next song starts to play.
- *
- * This is a copy of the queue's "single" flag most of the
- * time.
- */
- bool border_pause;
-};
-
-struct player_control *
-pc_new(unsigned buffer_chunks, unsigned buffered_before_play);
-
-void
-pc_free(struct player_control *pc);
-
-/**
- * Locks the #player_control object.
- */
-static inline void
-player_lock(struct player_control *pc)
-{
- g_mutex_lock(pc->mutex);
-}
-
-/**
- * Unlocks the #player_control object.
- */
-static inline void
-player_unlock(struct player_control *pc)
-{
- g_mutex_unlock(pc->mutex);
-}
-
-/**
- * Waits for a signal on the #player_control object. This function is
- * only valid in the player thread. The object must be locked prior
- * to calling this function.
- */
-static inline void
-player_wait(struct player_control *pc)
-{
- g_cond_wait(pc->cond, pc->mutex);
-}
-
-/**
- * Waits for a signal on the #player_control object. This function is
- * only valid in the player thread. The #decoder_control object must
- * be locked prior to calling this function.
- *
- * Note the small difference to the player_wait() function!
- */
-void
-player_wait_decoder(struct player_control *pc, struct decoder_control *dc);
-
-/**
- * Signals the #player_control object. The object should be locked
- * prior to calling this function.
- */
-static inline void
-player_signal(struct player_control *pc)
-{
- g_cond_signal(pc->cond);
-}
-
-/**
- * Signals the #player_control object. The object is temporarily
- * locked by this function.
- */
-static inline void
-player_lock_signal(struct player_control *pc)
-{
- player_lock(pc);
- player_signal(pc);
- player_unlock(pc);
-}
-
-/**
- * Call this function when the specified song pointer is about to be
- * invalidated. This makes sure that player_control.errored_song does
- * not point to an invalid pointer.
- */
-void
-pc_song_deleted(struct player_control *pc, const struct song *song);
-
-void
-pc_play(struct player_control *pc, struct song *song);
-
-/**
- * see PLAYER_COMMAND_CANCEL
- */
-void
-pc_cancel(struct player_control *pc);
-
-void
-pc_set_pause(struct player_control *pc, bool pause_flag);
-
-void
-pc_pause(struct player_control *pc);
-
-/**
- * Set the player's #border_pause flag.
- */
-void
-pc_set_border_pause(struct player_control *pc, bool border_pause);
-
-void
-pc_kill(struct player_control *pc);
-
-void
-pc_get_status(struct player_control *pc, struct player_status *status);
-
-enum player_state
-pc_get_state(struct player_control *pc);
-
-void
-pc_clear_error(struct player_control *pc);
-
-/**
- * Returns the human-readable message describing the last error during
- * playback, NULL if no error occurred. The caller has to free the
- * returned string.
- */
-char *
-pc_get_error_message(struct player_control *pc);
-
-enum player_error
-pc_get_error(struct player_control *pc);
-
-void
-pc_stop(struct player_control *pc);
-
-void
-pc_update_audio(struct player_control *pc);
-
-void
-pc_enqueue_song(struct player_control *pc, struct song *song);
-
-/**
- * Makes the player thread seek the specified song to a position.
- *
- * @return true on success, false on failure (e.g. if MPD isn't
- * playing currently)
- */
-bool
-pc_seek(struct player_control *pc, struct song *song, float seek_time);
-
-void
-pc_set_cross_fade(struct player_control *pc, float cross_fade_seconds);
-
-float
-pc_get_cross_fade(const struct player_control *pc);
-
-void
-pc_set_mixramp_db(struct player_control *pc, float mixramp_db);
-
-float
-pc_get_mixramp_db(const struct player_control *pc);
-
-void
-pc_set_mixramp_delay(struct player_control *pc, float mixramp_delay_seconds);
-
-float
-pc_get_mixramp_delay(const struct player_control *pc);
-
-double
-pc_get_total_play_time(const struct player_control *pc);
-
-#endif
diff --git a/src/player_thread.c b/src/player_thread.c
deleted file mode 100644
index ac0b0579e..000000000
--- a/src/player_thread.c
+++ /dev/null
@@ -1,1175 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "player_thread.h"
-#include "player_control.h"
-#include "decoder_control.h"
-#include "decoder_thread.h"
-#include "output_all.h"
-#include "pcm_volume.h"
-#include "path.h"
-#include "event_pipe.h"
-#include "crossfade.h"
-#include "song.h"
-#include "tag.h"
-#include "pipe.h"
-#include "chunk.h"
-#include "idle.h"
-#include "main.h"
-#include "buffer.h"
-#include "mpd_error.h"
-
-#include <glib.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "player_thread"
-
-enum xfade_state {
- XFADE_DISABLED = -1,
- XFADE_UNKNOWN = 0,
- XFADE_ENABLED = 1
-};
-
-struct player {
- struct player_control *pc;
-
- struct decoder_control *dc;
-
- struct music_pipe *pipe;
-
- /**
- * are we waiting for buffered_before_play?
- */
- bool buffering;
-
- /**
- * true if the decoder is starting and did not provide data
- * yet
- */
- bool decoder_starting;
-
- /**
- * is the player paused?
- */
- bool paused;
-
- /**
- * is there a new song in pc.next_song?
- */
- bool queued;
-
- /**
- * Was any audio output opened successfully? It might have
- * failed meanwhile, but was not explicitly closed by the
- * player thread. When this flag is unset, some output
- * methods must not be called.
- */
- bool output_open;
-
- /**
- * the song currently being played
- */
- struct song *song;
-
- /**
- * is cross fading enabled?
- */
- enum xfade_state xfade;
-
- /**
- * has cross-fading begun?
- */
- bool cross_fading;
-
- /**
- * The number of chunks used for crossfading.
- */
- unsigned cross_fade_chunks;
-
- /**
- * The tag of the "next" song during cross-fade. It is
- * postponed, and sent to the output thread when the new song
- * really begins.
- */
- struct tag *cross_fade_tag;
-
- /**
- * The current audio format for the audio outputs.
- */
- struct audio_format play_audio_format;
-
- /**
- * The time stamp of the chunk most recently sent to the
- * output thread. This attribute is only used if
- * audio_output_all_get_elapsed_time() didn't return a usable
- * value; the output thread can estimate the elapsed time more
- * precisely.
- */
- float elapsed_time;
-};
-
-static struct music_buffer *player_buffer;
-
-static void
-player_command_finished_locked(struct player_control *pc)
-{
- assert(pc->command != PLAYER_COMMAND_NONE);
-
- pc->command = PLAYER_COMMAND_NONE;
- g_cond_signal(main_cond);
-}
-
-static void
-player_command_finished(struct player_control *pc)
-{
- player_lock(pc);
- player_command_finished_locked(pc);
- player_unlock(pc);
-}
-
-/**
- * Start the decoder.
- *
- * Player lock is not held.
- */
-static void
-player_dc_start(struct player *player, struct music_pipe *pipe)
-{
- struct player_control *pc = player->pc;
- struct decoder_control *dc = player->dc;
-
- assert(player->queued || pc->command == PLAYER_COMMAND_SEEK);
- assert(pc->next_song != NULL);
-
- unsigned start_ms = pc->next_song->start_ms;
- if (pc->command == PLAYER_COMMAND_SEEK)
- start_ms += (unsigned)(pc->seek_where * 1000);
-
- dc_start(dc, pc->next_song,
- start_ms, pc->next_song->end_ms,
- player_buffer, pipe);
-}
-
-/**
- * Is the decoder still busy on the same song as the player?
- *
- * Note: this function does not check if the decoder is already
- * finished.
- */
-static bool
-player_dc_at_current_song(const struct player *player)
-{
- assert(player != NULL);
- assert(player->pipe != NULL);
-
- return player->dc->pipe == player->pipe;
-}
-
-/**
- * Returns true if the decoder is decoding the next song (or has begun
- * decoding it, or has finished doing it), and the player hasn't
- * switched to that song yet.
- */
-static bool
-player_dc_at_next_song(const struct player *player)
-{
- return player->dc->pipe != NULL && !player_dc_at_current_song(player);
-}
-
-/**
- * Stop the decoder and clears (and frees) its music pipe.
- *
- * Player lock is not held.
- */
-static void
-player_dc_stop(struct player *player)
-{
- struct decoder_control *dc = player->dc;
-
- dc_stop(dc);
-
- if (dc->pipe != NULL) {
- /* clear and free the decoder pipe */
-
- music_pipe_clear(dc->pipe, player_buffer);
-
- if (dc->pipe != player->pipe)
- music_pipe_free(dc->pipe);
-
- dc->pipe = NULL;
- }
-}
-
-/**
- * After the decoder has been started asynchronously, wait for the
- * "START" command to finish. The decoder may not be initialized yet,
- * i.e. there is no audio_format information yet.
- *
- * The player lock is not held.
- */
-static bool
-player_wait_for_decoder(struct player *player)
-{
- struct player_control *pc = player->pc;
- struct decoder_control *dc = player->dc;
-
- assert(player->queued || pc->command == PLAYER_COMMAND_SEEK);
- assert(pc->next_song != NULL);
-
- player->queued = false;
-
- if (decoder_lock_has_failed(dc)) {
- player_lock(pc);
- pc->errored_song = dc->song;
- pc->error = PLAYER_ERROR_FILE;
- pc->next_song = NULL;
- player_unlock(pc);
-
- return false;
- }
-
- player->song = pc->next_song;
- player->elapsed_time = 0.0;
-
- /* set the "starting" flag, which will be cleared by
- player_check_decoder_startup() */
- player->decoder_starting = true;
-
- player_lock(pc);
-
- /* update player_control's song information */
- pc->total_time = song_get_duration(pc->next_song);
- pc->bit_rate = 0;
- audio_format_clear(&pc->audio_format);
-
- /* clear the queued song */
- pc->next_song = NULL;
-
- player_unlock(pc);
-
- /* call syncPlaylistWithQueue() in the main thread */
- event_pipe_emit(PIPE_EVENT_PLAYLIST);
-
- return true;
-}
-
-/**
- * Returns the real duration of the song, comprising the duration
- * indicated by the decoder plugin.
- */
-static double
-real_song_duration(const struct song *song, double decoder_duration)
-{
- assert(song != NULL);
-
- if (decoder_duration <= 0.0)
- /* the decoder plugin didn't provide information; fall
- back to song_get_duration() */
- return song_get_duration(song);
-
- if (song->end_ms > 0 && song->end_ms / 1000.0 < decoder_duration)
- return (song->end_ms - song->start_ms) / 1000.0;
-
- return decoder_duration - song->start_ms / 1000.0;
-}
-
-/**
- * Wrapper for audio_output_all_open(). Upon failure, it pauses the
- * player.
- *
- * @return true on success
- */
-static bool
-player_open_output(struct player *player)
-{
- struct player_control *pc = player->pc;
-
- assert(audio_format_defined(&player->play_audio_format));
- assert(pc->state == PLAYER_STATE_PLAY ||
- pc->state == PLAYER_STATE_PAUSE);
-
- if (audio_output_all_open(&player->play_audio_format, player_buffer)) {
- player->output_open = true;
- player->paused = false;
-
- player_lock(pc);
- pc->state = PLAYER_STATE_PLAY;
- player_unlock(pc);
-
- idle_add(IDLE_PLAYER);
-
- return true;
- } else {
- player->output_open = false;
-
- /* pause: the user may resume playback as soon as an
- audio output becomes available */
- player->paused = true;
-
- player_lock(pc);
- pc->error = PLAYER_ERROR_AUDIO;
- pc->state = PLAYER_STATE_PAUSE;
- player_unlock(pc);
-
- idle_add(IDLE_PLAYER);
-
- return false;
- }
-}
-
-/**
- * The decoder has acknowledged the "START" command (see
- * player_wait_for_decoder()). This function checks if the decoder
- * initialization has completed yet.
- *
- * The player lock is not held.
- */
-static bool
-player_check_decoder_startup(struct player *player)
-{
- struct player_control *pc = player->pc;
- struct decoder_control *dc = player->dc;
-
- assert(player->decoder_starting);
-
- decoder_lock(dc);
-
- if (decoder_has_failed(dc)) {
- /* the decoder failed */
- decoder_unlock(dc);
-
- player_lock(pc);
- pc->errored_song = dc->song;
- pc->error = PLAYER_ERROR_FILE;
- player_unlock(pc);
-
- return false;
- } else if (!decoder_is_starting(dc)) {
- /* the decoder is ready and ok */
-
- decoder_unlock(dc);
-
- if (player->output_open &&
- !audio_output_all_wait(pc, 1))
- /* the output devices havn't finished playing
- all chunks yet - wait for that */
- return true;
-
- player_lock(pc);
- pc->total_time = real_song_duration(dc->song, dc->total_time);
- pc->audio_format = dc->in_audio_format;
- player_unlock(pc);
-
- idle_add(IDLE_PLAYER);
-
- player->play_audio_format = dc->out_audio_format;
- player->decoder_starting = false;
-
- if (!player->paused && !player_open_output(player)) {
- char *uri = song_get_uri(dc->song);
- g_warning("problems opening audio device "
- "while playing \"%s\"", uri);
- g_free(uri);
-
- return true;
- }
-
- return true;
- } else {
- /* the decoder is not yet ready; wait
- some more */
- player_wait_decoder(pc, dc);
- decoder_unlock(dc);
-
- return true;
- }
-}
-
-/**
- * Sends a chunk of silence to the audio outputs. This is called when
- * there is not enough decoded data in the pipe yet, to prevent
- * underruns in the hardware buffers.
- *
- * The player lock is not held.
- */
-static bool
-player_send_silence(struct player *player)
-{
- assert(player->output_open);
- assert(audio_format_defined(&player->play_audio_format));
-
- struct music_chunk *chunk = music_buffer_allocate(player_buffer);
- if (chunk == NULL) {
- g_warning("Failed to allocate silence buffer");
- return false;
- }
-
-#ifndef NDEBUG
- chunk->audio_format = player->play_audio_format;
-#endif
-
- size_t frame_size =
- audio_format_frame_size(&player->play_audio_format);
- /* this formula ensures that we don't send
- partial frames */
- unsigned num_frames = sizeof(chunk->data) / frame_size;
-
- chunk->times = -1.0; /* undefined time stamp */
- chunk->length = num_frames * frame_size;
- memset(chunk->data, 0, chunk->length);
-
- if (!audio_output_all_play(chunk)) {
- music_buffer_return(player_buffer, chunk);
- return false;
- }
-
- return true;
-}
-
-/**
- * This is the handler for the #PLAYER_COMMAND_SEEK command.
- *
- * The player lock is not held.
- */
-static bool player_seek_decoder(struct player *player)
-{
- struct player_control *pc = player->pc;
- struct song *song = pc->next_song;
- struct decoder_control *dc = player->dc;
-
- assert(pc->next_song != NULL);
-
- const unsigned start_ms = song->start_ms;
-
- if (decoder_current_song(dc) != song) {
- /* the decoder is already decoding the "next" song -
- stop it and start the previous song again */
-
- player_dc_stop(player);
-
- /* clear music chunks which might still reside in the
- pipe */
- music_pipe_clear(player->pipe, player_buffer);
-
- /* re-start the decoder */
- player_dc_start(player, player->pipe);
- if (!player_wait_for_decoder(player)) {
- /* decoder failure */
- player_command_finished(pc);
- return false;
- }
- } else {
- if (!player_dc_at_current_song(player)) {
- /* the decoder is already decoding the "next" song,
- but it is the same song file; exchange the pipe */
- music_pipe_clear(player->pipe, player_buffer);
- music_pipe_free(player->pipe);
- player->pipe = dc->pipe;
- }
-
- pc->next_song = NULL;
- player->queued = false;
- }
-
- /* wait for the decoder to complete initialization */
-
- while (player->decoder_starting) {
- if (!player_check_decoder_startup(player)) {
- /* decoder failure */
- player_command_finished(pc);
- return false;
- }
- }
-
- /* send the SEEK command */
-
- double where = pc->seek_where;
- if (where > pc->total_time)
- where = pc->total_time - 0.1;
- if (where < 0.0)
- where = 0.0;
-
- if (!dc_seek(dc, where + start_ms / 1000.0)) {
- /* decoder failure */
- player_command_finished(pc);
- return false;
- }
-
- player->elapsed_time = where;
-
- player_command_finished(pc);
-
- player->xfade = XFADE_UNKNOWN;
-
- /* re-fill the buffer after seeking */
- player->buffering = true;
-
- audio_output_all_cancel();
-
- return true;
-}
-
-/**
- * Player lock must be held before calling.
- */
-static void player_process_command(struct player *player)
-{
- struct player_control *pc = player->pc;
- G_GNUC_UNUSED struct decoder_control *dc = player->dc;
-
- switch (pc->command) {
- case PLAYER_COMMAND_NONE:
- case PLAYER_COMMAND_STOP:
- case PLAYER_COMMAND_EXIT:
- case PLAYER_COMMAND_CLOSE_AUDIO:
- break;
-
- case PLAYER_COMMAND_UPDATE_AUDIO:
- player_unlock(pc);
- audio_output_all_enable_disable();
- player_lock(pc);
- player_command_finished_locked(pc);
- break;
-
- case PLAYER_COMMAND_QUEUE:
- assert(pc->next_song != NULL);
- assert(!player->queued);
- assert(!player_dc_at_next_song(player));
-
- player->queued = true;
- player_command_finished_locked(pc);
- break;
-
- case PLAYER_COMMAND_PAUSE:
- player_unlock(pc);
-
- player->paused = !player->paused;
- if (player->paused) {
- audio_output_all_pause();
- player_lock(pc);
-
- pc->state = PLAYER_STATE_PAUSE;
- } else if (!audio_format_defined(&player->play_audio_format)) {
- /* the decoder hasn't provided an audio format
- yet - don't open the audio device yet */
- player_lock(pc);
-
- pc->state = PLAYER_STATE_PLAY;
- } else {
- player_open_output(player);
-
- player_lock(pc);
- }
-
- player_command_finished_locked(pc);
- break;
-
- case PLAYER_COMMAND_SEEK:
- player_unlock(pc);
- player_seek_decoder(player);
- player_lock(pc);
- break;
-
- case PLAYER_COMMAND_CANCEL:
- if (pc->next_song == NULL) {
- /* the cancel request arrived too late, we're
- already playing the queued song... stop
- everything now */
- pc->command = PLAYER_COMMAND_STOP;
- return;
- }
-
- if (player_dc_at_next_song(player)) {
- /* the decoder is already decoding the song -
- stop it and reset the position */
- player_unlock(pc);
- player_dc_stop(player);
- player_lock(pc);
- }
-
- pc->next_song = NULL;
- player->queued = false;
- player_command_finished_locked(pc);
- break;
-
- case PLAYER_COMMAND_REFRESH:
- if (player->output_open && !player->paused) {
- player_unlock(pc);
- audio_output_all_check();
- player_lock(pc);
- }
-
- pc->elapsed_time = audio_output_all_get_elapsed_time();
- if (pc->elapsed_time < 0.0)
- pc->elapsed_time = player->elapsed_time;
-
- player_command_finished_locked(pc);
- break;
- }
-}
-
-static void
-update_song_tag(struct song *song, const struct tag *new_tag)
-{
- if (song_is_file(song))
- /* don't update tags of local files, only remote
- streams may change tags dynamically */
- return;
-
- struct tag *old_tag = song->tag;
- song->tag = tag_dup(new_tag);
-
- if (old_tag != NULL)
- tag_free(old_tag);
-
- /* the main thread will update the playlist version when he
- receives this event */
- event_pipe_emit(PIPE_EVENT_TAG);
-
- /* notify all clients that the tag of the current song has
- changed */
- idle_add(IDLE_PLAYER);
-}
-
-/**
- * Plays a #music_chunk object (after applying software volume). If
- * it contains a (stream) tag, copy it to the current song, so MPD's
- * playlist reflects the new stream tag.
- *
- * Player lock is not held.
- */
-static bool
-play_chunk(struct player_control *pc,
- struct song *song, struct music_chunk *chunk,
- const struct audio_format *format)
-{
- assert(music_chunk_check_format(chunk, format));
-
- if (chunk->tag != NULL)
- update_song_tag(song, chunk->tag);
-
- if (chunk->length == 0) {
- music_buffer_return(player_buffer, chunk);
- return true;
- }
-
- player_lock(pc);
- pc->bit_rate = chunk->bit_rate;
- player_unlock(pc);
-
- /* send the chunk to the audio outputs */
-
- if (!audio_output_all_play(chunk))
- return false;
-
- pc->total_play_time += (double)chunk->length /
- audio_format_time_to_size(format);
- return true;
-}
-
-/**
- * Obtains the next chunk from the music pipe, optionally applies
- * cross-fading, and sends it to all audio outputs.
- *
- * @return true on success, false on error (playback will be stopped)
- */
-static bool
-play_next_chunk(struct player *player)
-{
- struct player_control *pc = player->pc;
- struct decoder_control *dc = player->dc;
-
- if (!audio_output_all_wait(pc, 64))
- /* the output pipe is still large enough, don't send
- another chunk */
- return true;
-
- unsigned cross_fade_position;
- struct music_chunk *chunk = NULL;
- if (player->xfade == XFADE_ENABLED &&
- player_dc_at_next_song(player) &&
- (cross_fade_position = music_pipe_size(player->pipe))
- <= player->cross_fade_chunks) {
- /* perform cross fade */
- struct music_chunk *other_chunk =
- music_pipe_shift(dc->pipe);
-
- if (!player->cross_fading) {
- /* beginning of the cross fade - adjust
- crossFadeChunks which might be bigger than
- the remaining number of chunks in the old
- song */
- player->cross_fade_chunks = cross_fade_position;
- player->cross_fading = true;
- }
-
- if (other_chunk != NULL) {
- chunk = music_pipe_shift(player->pipe);
- assert(chunk != NULL);
- assert(chunk->other == NULL);
-
- /* don't send the tags of the new song (which
- is being faded in) yet; postpone it until
- the current song is faded out */
- player->cross_fade_tag =
- tag_merge_replace(player->cross_fade_tag,
- other_chunk->tag);
- other_chunk->tag = NULL;
-
- if (isnan(pc->mixramp_delay_seconds)) {
- chunk->mix_ratio = ((float)cross_fade_position)
- / player->cross_fade_chunks;
- } else {
- chunk->mix_ratio = nan("");
- }
-
- if (music_chunk_is_empty(other_chunk)) {
- /* the "other" chunk was a music_chunk
- which had only a tag, but no music
- data - we cannot cross-fade that;
- but since this happens only at the
- beginning of the new song, we can
- easily recover by throwing it away
- now */
- music_buffer_return(player_buffer,
- other_chunk);
- other_chunk = NULL;
- }
-
- chunk->other = other_chunk;
- } else {
- /* there are not enough decoded chunks yet */
-
- decoder_lock(dc);
-
- if (decoder_is_idle(dc)) {
- /* the decoder isn't running, abort
- cross fading */
- decoder_unlock(dc);
-
- player->xfade = XFADE_DISABLED;
- } else {
- /* wait for the decoder */
- decoder_signal(dc);
- player_wait_decoder(pc, dc);
- decoder_unlock(dc);
-
- return true;
- }
- }
- }
-
- if (chunk == NULL)
- chunk = music_pipe_shift(player->pipe);
-
- assert(chunk != NULL);
-
- /* insert the postponed tag if cross-fading is finished */
-
- if (player->xfade != XFADE_ENABLED && player->cross_fade_tag != NULL) {
- chunk->tag = tag_merge_replace(chunk->tag,
- player->cross_fade_tag);
- player->cross_fade_tag = NULL;
- }
-
- /* play the current chunk */
-
- if (!play_chunk(player->pc, player->song, chunk,
- &player->play_audio_format)) {
- music_buffer_return(player_buffer, chunk);
-
- player_lock(pc);
-
- pc->error = PLAYER_ERROR_AUDIO;
-
- /* pause: the user may resume playback as soon as an
- audio output becomes available */
- pc->state = PLAYER_STATE_PAUSE;
- player->paused = true;
-
- player_unlock(pc);
-
- idle_add(IDLE_PLAYER);
-
- return false;
- }
-
- /* this formula should prevent that the decoder gets woken up
- with each chunk; it is more efficient to make it decode a
- larger block at a time */
- decoder_lock(dc);
- if (!decoder_is_idle(dc) &&
- music_pipe_size(dc->pipe) <= (pc->buffered_before_play +
- music_buffer_size(player_buffer) * 3) / 4)
- decoder_signal(dc);
- decoder_unlock(dc);
-
- return true;
-}
-
-/**
- * This is called at the border between two songs: the audio output
- * has consumed all chunks of the current song, and we should start
- * sending chunks from the next one.
- *
- * The player lock is not held.
- *
- * @return true on success, false on error (playback will be stopped)
- */
-static bool
-player_song_border(struct player *player)
-{
- player->xfade = XFADE_UNKNOWN;
-
- char *uri = song_get_uri(player->song);
- g_message("played \"%s\"", uri);
- g_free(uri);
-
- music_pipe_free(player->pipe);
- player->pipe = player->dc->pipe;
-
- audio_output_all_song_border();
-
- if (!player_wait_for_decoder(player))
- return false;
-
- struct player_control *const pc = player->pc;
- player_lock(pc);
-
- const bool border_pause = pc->border_pause;
- if (border_pause) {
- player->paused = true;
- pc->state = PLAYER_STATE_PAUSE;
- }
-
- player_unlock(pc);
-
- if (border_pause)
- idle_add(IDLE_PLAYER);
-
- return true;
-}
-
-/*
- * The main loop of the player thread, during playback. This is
- * basically a state machine, which multiplexes data between the
- * decoder thread and the output threads.
- */
-static void do_play(struct player_control *pc, struct decoder_control *dc)
-{
- struct player player = {
- .pc = pc,
- .dc = dc,
- .buffering = true,
- .decoder_starting = false,
- .paused = false,
- .queued = true,
- .output_open = false,
- .song = NULL,
- .xfade = XFADE_UNKNOWN,
- .cross_fading = false,
- .cross_fade_chunks = 0,
- .cross_fade_tag = NULL,
- .elapsed_time = 0.0,
- };
-
- player_unlock(pc);
-
- player.pipe = music_pipe_new();
-
- player_dc_start(&player, player.pipe);
- if (!player_wait_for_decoder(&player)) {
- player_dc_stop(&player);
- player_command_finished(pc);
- music_pipe_free(player.pipe);
- event_pipe_emit(PIPE_EVENT_PLAYLIST);
- player_lock(pc);
- return;
- }
-
- player_lock(pc);
- pc->state = PLAYER_STATE_PLAY;
-
- if (pc->command == PLAYER_COMMAND_SEEK)
- player.elapsed_time = pc->seek_where;
-
- player_command_finished_locked(pc);
-
- while (true) {
- player_process_command(&player);
- if (pc->command == PLAYER_COMMAND_STOP ||
- pc->command == PLAYER_COMMAND_EXIT ||
- pc->command == PLAYER_COMMAND_CLOSE_AUDIO) {
- player_unlock(pc);
- audio_output_all_cancel();
- break;
- }
-
- player_unlock(pc);
-
- if (player.buffering) {
- /* buffering at the start of the song - wait
- until the buffer is large enough, to
- prevent stuttering on slow machines */
-
- if (music_pipe_size(player.pipe) < pc->buffered_before_play &&
- !decoder_lock_is_idle(dc)) {
- /* not enough decoded buffer space yet */
-
- if (!player.paused &&
- player.output_open &&
- audio_output_all_check() < 4 &&
- !player_send_silence(&player))
- break;
-
- decoder_lock(dc);
- /* XXX race condition: check decoder again */
- player_wait_decoder(pc, dc);
- decoder_unlock(dc);
- player_lock(pc);
- continue;
- } else {
- /* buffering is complete */
- player.buffering = false;
- }
- }
-
- if (player.decoder_starting) {
- /* wait until the decoder is initialized completely */
-
- if (!player_check_decoder_startup(&player))
- break;
-
- player_lock(pc);
- continue;
- }
-
-#ifndef NDEBUG
- /*
- music_pipe_check_format(&play_audio_format,
- player.next_song_chunk,
- &dc->out_audio_format);
- */
-#endif
-
- if (decoder_lock_is_idle(dc) && player.queued &&
- dc->pipe == player.pipe) {
- /* the decoder has finished the current song;
- make it decode the next song */
-
- assert(dc->pipe == NULL || dc->pipe == player.pipe);
-
- player_dc_start(&player, music_pipe_new());
- }
-
- if (/* no cross-fading if MPD is going to pause at the
- end of the current song */
- !pc->border_pause &&
- player_dc_at_next_song(&player) &&
- player.xfade == XFADE_UNKNOWN &&
- !decoder_lock_is_starting(dc)) {
- /* enable cross fading in this song? if yes,
- calculate how many chunks will be required
- for it */
- player.cross_fade_chunks =
- cross_fade_calc(pc->cross_fade_seconds, dc->total_time,
- pc->mixramp_db,
- pc->mixramp_delay_seconds,
- dc->replay_gain_db,
- dc->replay_gain_prev_db,
- dc->mixramp_start,
- dc->mixramp_prev_end,
- &dc->out_audio_format,
- &player.play_audio_format,
- music_buffer_size(player_buffer) -
- pc->buffered_before_play);
- if (player.cross_fade_chunks > 0) {
- player.xfade = XFADE_ENABLED;
- player.cross_fading = false;
- } else
- /* cross fading is disabled or the
- next song is too short */
- player.xfade = XFADE_DISABLED;
- }
-
- if (player.paused) {
- player_lock(pc);
-
- if (pc->command == PLAYER_COMMAND_NONE)
- player_wait(pc);
- continue;
- } else if (!music_pipe_empty(player.pipe)) {
- /* at least one music chunk is ready - send it
- to the audio output */
-
- play_next_chunk(&player);
- } else if (audio_output_all_check() > 0) {
- /* not enough data from decoder, but the
- output thread is still busy, so it's
- okay */
-
- /* XXX synchronize in a better way */
- g_usleep(10000);
- } else if (player_dc_at_next_song(&player)) {
- /* at the beginning of a new song */
-
- if (!player_song_border(&player))
- break;
- } else if (decoder_lock_is_idle(dc)) {
- /* check the size of the pipe again, because
- the decoder thread may have added something
- since we last checked */
- if (music_pipe_empty(player.pipe)) {
- /* wait for the hardware to finish
- playback */
- audio_output_all_drain();
- break;
- }
- } else if (player.output_open) {
- /* the decoder is too busy and hasn't provided
- new PCM data in time: send silence (if the
- output pipe is empty) */
- if (!player_send_silence(&player))
- break;
- }
-
- player_lock(pc);
- }
-
- player_dc_stop(&player);
-
- music_pipe_clear(player.pipe, player_buffer);
- music_pipe_free(player.pipe);
-
- if (player.cross_fade_tag != NULL)
- tag_free(player.cross_fade_tag);
-
- player_lock(pc);
-
- if (player.queued) {
- assert(pc->next_song != NULL);
- pc->next_song = NULL;
- }
-
- pc->state = PLAYER_STATE_STOP;
-
- player_unlock(pc);
-
- event_pipe_emit(PIPE_EVENT_PLAYLIST);
-
- player_lock(pc);
-}
-
-static gpointer
-player_task(gpointer arg)
-{
- struct player_control *pc = arg;
-
- struct decoder_control *dc = dc_new(pc->cond);
- decoder_thread_start(dc);
-
- player_buffer = music_buffer_new(pc->buffer_chunks);
-
- player_lock(pc);
-
- while (1) {
- switch (pc->command) {
- case PLAYER_COMMAND_SEEK:
- case PLAYER_COMMAND_QUEUE:
- assert(pc->next_song != NULL);
-
- do_play(pc, dc);
- break;
-
- case PLAYER_COMMAND_STOP:
- player_unlock(pc);
- audio_output_all_cancel();
- player_lock(pc);
-
- /* fall through */
-
- case PLAYER_COMMAND_PAUSE:
- pc->next_song = NULL;
- player_command_finished_locked(pc);
- break;
-
- case PLAYER_COMMAND_CLOSE_AUDIO:
- player_unlock(pc);
-
- audio_output_all_release();
-
- player_lock(pc);
- player_command_finished_locked(pc);
-
-#ifndef NDEBUG
- /* in the DEBUG build, check for leaked
- music_chunk objects by freeing the
- music_buffer */
- music_buffer_free(player_buffer);
- player_buffer = music_buffer_new(pc->buffer_chunks);
-#endif
-
- break;
-
- case PLAYER_COMMAND_UPDATE_AUDIO:
- player_unlock(pc);
- audio_output_all_enable_disable();
- player_lock(pc);
- player_command_finished_locked(pc);
- break;
-
- case PLAYER_COMMAND_EXIT:
- player_unlock(pc);
-
- dc_quit(dc);
- dc_free(dc);
- audio_output_all_close();
- music_buffer_free(player_buffer);
-
- player_command_finished(pc);
- return NULL;
-
- case PLAYER_COMMAND_CANCEL:
- pc->next_song = NULL;
- player_command_finished_locked(pc);
- break;
-
- case PLAYER_COMMAND_REFRESH:
- /* no-op when not playing */
- player_command_finished_locked(pc);
- break;
-
- case PLAYER_COMMAND_NONE:
- player_wait(pc);
- break;
- }
- }
-}
-
-void
-player_create(struct player_control *pc)
-{
- assert(pc->thread == NULL);
-
- GError *e = NULL;
- pc->thread = g_thread_create(player_task, pc, true, &e);
- if (pc->thread == NULL)
- MPD_ERROR("Failed to spawn player task: %s", e->message);
-}
diff --git a/src/player_thread.h b/src/player_thread.h
deleted file mode 100644
index 7373eb438..000000000
--- a/src/player_thread.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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.
- */
-
-/* \file
- *
- * The player thread controls the playback. It acts as a bridge
- * between the decoder thread and the output thread(s): it receives
- * #music_chunk objects from the decoder, optionally mixes them
- * (cross-fading), applies software volume, and sends them to the
- * audio outputs via audio_output_all_play().
- *
- * It is controlled by the main thread (the playlist code), see
- * player_control.h. The playlist enqueues new songs into the player
- * thread and sends it commands.
- *
- * The player thread itself does not do any I/O. It synchronizes with
- * other threads via #GMutex and #GCond objects, and passes
- * #music_chunk instances around in #music_pipe objects.
- */
-
-#ifndef MPD_PLAYER_THREAD_H
-#define MPD_PLAYER_THREAD_H
-
-struct player_control;
-
-void
-player_create(struct player_control *pc);
-
-#endif
diff --git a/src/playlist.c b/src/playlist.c
deleted file mode 100644
index dc6d8c340..000000000
--- a/src/playlist.c
+++ /dev/null
@@ -1,452 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "playlist_internal.h"
-#include "playlist_save.h"
-#include "player_control.h"
-#include "command.h"
-#include "tag.h"
-#include "song.h"
-#include "conf.h"
-#include "stored_playlist.h"
-#include "idle.h"
-
-#include <glib.h>
-
-#include <assert.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "playlist"
-
-void
-playlist_increment_version_all(struct playlist *playlist)
-{
- queue_modify_all(&playlist->queue);
- idle_add(IDLE_PLAYLIST);
-}
-
-void
-playlist_tag_changed(struct playlist *playlist)
-{
- if (!playlist->playing)
- return;
-
- assert(playlist->current >= 0);
-
- queue_modify(&playlist->queue, playlist->current);
- idle_add(IDLE_PLAYLIST);
-}
-
-void
-playlist_init(struct playlist *playlist)
-{
- queue_init(&playlist->queue,
- config_get_positive(CONF_MAX_PLAYLIST_LENGTH,
- DEFAULT_PLAYLIST_MAX_LENGTH));
-
- playlist->queued = -1;
- playlist->current = -1;
-}
-
-void
-playlist_finish(struct playlist *playlist)
-{
- queue_finish(&playlist->queue);
-}
-
-/**
- * Queue a song, addressed by its order number.
- */
-static void
-playlist_queue_song_order(struct playlist *playlist, struct player_control *pc,
- unsigned order)
-{
- struct song *song;
- char *uri;
-
- assert(queue_valid_order(&playlist->queue, order));
-
- playlist->queued = order;
-
- song = queue_get_order(&playlist->queue, order);
- uri = song_get_uri(song);
- g_debug("queue song %i:\"%s\"", playlist->queued, uri);
- g_free(uri);
-
- pc_enqueue_song(pc, song);
-}
-
-/**
- * Called if the player thread has started playing the "queued" song.
- */
-static void
-playlist_song_started(struct playlist *playlist, struct player_control *pc)
-{
- assert(pc->next_song == NULL);
- assert(playlist->queued >= -1);
-
- /* queued song has started: copy queued to current,
- and notify the clients */
-
- int current = playlist->current;
- playlist->current = playlist->queued;
- playlist->queued = -1;
-
- if(playlist->queue.consume)
- playlist_delete(playlist, pc,
- queue_order_to_position(&playlist->queue,
- current));
-
- idle_add(IDLE_PLAYER);
-}
-
-const struct song *
-playlist_get_queued_song(struct playlist *playlist)
-{
- if (!playlist->playing || playlist->queued < 0)
- return NULL;
-
- return queue_get_order(&playlist->queue, playlist->queued);
-}
-
-void
-playlist_update_queued_song(struct playlist *playlist,
- struct player_control *pc,
- const struct song *prev)
-{
- int next_order;
- const struct song *next_song;
-
- if (!playlist->playing)
- return;
-
- assert(!queue_is_empty(&playlist->queue));
- assert((playlist->queued < 0) == (prev == NULL));
-
- next_order = playlist->current >= 0
- ? queue_next_order(&playlist->queue, playlist->current)
- : 0;
-
- if (next_order == 0 && playlist->queue.random &&
- !playlist->queue.single) {
- /* shuffle the song order again, so we get a different
- order each time the playlist is played
- completely */
- unsigned current_position =
- queue_order_to_position(&playlist->queue,
- playlist->current);
-
- queue_shuffle_order(&playlist->queue);
-
- /* make sure that the playlist->current still points to
- the current song, after the song order has been
- shuffled */
- playlist->current =
- queue_position_to_order(&playlist->queue,
- current_position);
- }
-
- if (next_order >= 0)
- next_song = queue_get_order(&playlist->queue, next_order);
- else
- next_song = NULL;
-
- if (prev != NULL && next_song != prev) {
- /* clear the currently queued song */
- pc_cancel(pc);
- playlist->queued = -1;
- }
-
- if (next_order >= 0) {
- if (next_song != prev)
- playlist_queue_song_order(playlist, pc, next_order);
- else
- playlist->queued = next_order;
- }
-}
-
-void
-playlist_play_order(struct playlist *playlist, struct player_control *pc,
- int orderNum)
-{
- struct song *song;
- char *uri;
-
- playlist->playing = true;
- playlist->queued = -1;
-
- song = queue_get_order(&playlist->queue, orderNum);
-
- uri = song_get_uri(song);
- g_debug("play %i:\"%s\"", orderNum, uri);
- g_free(uri);
-
- pc_play(pc, song);
- playlist->current = orderNum;
-}
-
-static void
-playlist_resume_playback(struct playlist *playlist, struct player_control *pc);
-
-/**
- * This is the "PLAYLIST" event handler. It is invoked by the player
- * thread whenever it requests a new queued song, or when it exits.
- */
-void
-playlist_sync(struct playlist *playlist, struct player_control *pc)
-{
- if (!playlist->playing)
- /* this event has reached us out of sync: we aren't
- playing anymore; ignore the event */
- return;
-
- player_lock(pc);
- enum player_state pc_state = pc_get_state(pc);
- const struct song *pc_next_song = pc->next_song;
- player_unlock(pc);
-
- if (pc_state == PLAYER_STATE_STOP)
- /* the player thread has stopped: check if playback
- should be restarted with the next song. That can
- happen if the playlist isn't filling the queue fast
- enough */
- playlist_resume_playback(playlist, pc);
- else {
- /* check if the player thread has already started
- playing the queued song */
- if (pc_next_song == NULL && playlist->queued != -1)
- playlist_song_started(playlist, pc);
-
- player_lock(pc);
- pc_next_song = pc->next_song;
- player_unlock(pc);
-
- /* make sure the queued song is always set (if
- possible) */
- if (pc_next_song == NULL && playlist->queued < 0)
- playlist_update_queued_song(playlist, pc, NULL);
- }
-}
-
-/**
- * The player has stopped for some reason. Check the error, and
- * decide whether to re-start playback
- */
-static void
-playlist_resume_playback(struct playlist *playlist, struct player_control *pc)
-{
- enum player_error error;
-
- assert(playlist->playing);
- assert(pc_get_state(pc) == PLAYER_STATE_STOP);
-
- error = pc_get_error(pc);
- if (error == PLAYER_ERROR_NOERROR)
- playlist->error_count = 0;
- else
- ++playlist->error_count;
-
- if ((playlist->stop_on_error && error != PLAYER_ERROR_NOERROR) ||
- error == PLAYER_ERROR_AUDIO || error == PLAYER_ERROR_SYSTEM ||
- playlist->error_count >= queue_length(&playlist->queue))
- /* too many errors, or critical error: stop
- playback */
- playlist_stop(playlist, pc);
- else
- /* continue playback at the next song */
- playlist_next(playlist, pc);
-}
-
-bool
-playlist_get_repeat(const struct playlist *playlist)
-{
- return playlist->queue.repeat;
-}
-
-bool
-playlist_get_random(const struct playlist *playlist)
-{
- return playlist->queue.random;
-}
-
-bool
-playlist_get_single(const struct playlist *playlist)
-{
- return playlist->queue.single;
-}
-
-bool
-playlist_get_consume(const struct playlist *playlist)
-{
- return playlist->queue.consume;
-}
-
-void
-playlist_set_repeat(struct playlist *playlist, struct player_control *pc,
- bool status)
-{
- if (status == playlist->queue.repeat)
- return;
-
- struct queue *queue = &playlist->queue;
-
- queue->repeat = status;
-
- pc_set_border_pause(pc, queue->single && !queue->repeat);
-
- /* if the last song is currently being played, the "next song"
- might change when repeat mode is toggled */
- playlist_update_queued_song(playlist, pc,
- playlist_get_queued_song(playlist));
-
- idle_add(IDLE_OPTIONS);
-}
-
-static void
-playlist_order(struct playlist *playlist)
-{
- if (playlist->current >= 0)
- /* update playlist.current, order==position now */
- playlist->current = queue_order_to_position(&playlist->queue,
- playlist->current);
-
- queue_restore_order(&playlist->queue);
-}
-
-void
-playlist_set_single(struct playlist *playlist, struct player_control *pc,
- bool status)
-{
- if (status == playlist->queue.single)
- return;
-
- struct queue *queue = &playlist->queue;
-
- queue->single = status;
-
- pc_set_border_pause(pc, queue->single && !queue->repeat);
-
- /* if the last song is currently being played, the "next song"
- might change when single mode is toggled */
- playlist_update_queued_song(playlist, pc,
- playlist_get_queued_song(playlist));
-
- idle_add(IDLE_OPTIONS);
-}
-
-void
-playlist_set_consume(struct playlist *playlist, bool status)
-{
- if (status == playlist->queue.consume)
- return;
-
- playlist->queue.consume = status;
- idle_add(IDLE_OPTIONS);
-}
-
-void
-playlist_set_random(struct playlist *playlist, struct player_control *pc,
- bool status)
-{
- const struct song *queued;
-
- if (status == playlist->queue.random)
- return;
-
- queued = playlist_get_queued_song(playlist);
-
- playlist->queue.random = status;
-
- if (playlist->queue.random) {
- /* shuffle the queue order, but preserve
- playlist->current */
-
- int current_position =
- playlist->playing && playlist->current >= 0
- ? (int)queue_order_to_position(&playlist->queue,
- playlist->current)
- : -1;
-
- queue_shuffle_order(&playlist->queue);
-
- if (current_position >= 0) {
- /* make sure the current song is the first in
- the order list, so the whole rest of the
- playlist is played after that */
- unsigned current_order =
- queue_position_to_order(&playlist->queue,
- current_position);
- queue_swap_order(&playlist->queue, 0, current_order);
- playlist->current = 0;
- } else
- playlist->current = -1;
- } else
- playlist_order(playlist);
-
- playlist_update_queued_song(playlist, pc, queued);
-
- idle_add(IDLE_OPTIONS);
-}
-
-int
-playlist_get_current_song(const struct playlist *playlist)
-{
- if (playlist->current >= 0)
- return queue_order_to_position(&playlist->queue,
- playlist->current);
-
- return -1;
-}
-
-int
-playlist_get_next_song(const struct playlist *playlist)
-{
- if (playlist->current >= 0)
- {
- if (playlist->queue.single == 1 && playlist->queue.repeat == 1)
- return queue_order_to_position(&playlist->queue,
- playlist->current);
- else if (playlist->current + 1 < (int)queue_length(&playlist->queue))
- return queue_order_to_position(&playlist->queue,
- playlist->current + 1);
- else if (playlist->queue.repeat == 1)
- return queue_order_to_position(&playlist->queue, 0);
- }
-
- return -1;
-}
-
-unsigned long
-playlist_get_version(const struct playlist *playlist)
-{
- return playlist->queue.version;
-}
-
-int
-playlist_get_length(const struct playlist *playlist)
-{
- return queue_length(&playlist->queue);
-}
-
-unsigned
-playlist_get_song_id(const struct playlist *playlist, unsigned song)
-{
- return queue_position_to_id(&playlist->queue, song);
-}
diff --git a/src/playlist.h b/src/playlist.h
deleted file mode 100644
index a21bdf24a..000000000
--- a/src/playlist.h
+++ /dev/null
@@ -1,256 +0,0 @@
-/*
- * 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_PLAYLIST_H
-#define MPD_PLAYLIST_H
-
-#include "queue.h"
-#include "playlist_error.h"
-
-#include <stdbool.h>
-
-struct player_control;
-
-struct playlist {
- /**
- * The song queue - it contains the "real" playlist.
- */
- struct queue queue;
-
- /**
- * This value is true if the player is currently playing (or
- * should be playing).
- */
- bool playing;
-
- /**
- * If true, then any error is fatal; if false, MPD will
- * attempt to play the next song on non-fatal errors. During
- * seeking, this flag is set.
- */
- bool stop_on_error;
-
- /**
- * Number of errors since playback was started. If this
- * number exceeds the length of the playlist, MPD gives up,
- * because all songs have been tried.
- */
- unsigned error_count;
-
- /**
- * The "current song pointer". This is the song which is
- * played when we get the "play" command. It is also the song
- * which is currently being played.
- */
- int current;
-
- /**
- * The "next" song to be played, when the current one
- * finishes. The decoder thread may start decoding and
- * buffering it, while the "current" song is still playing.
- *
- * This variable is only valid if #playing is true.
- */
- int queued;
-};
-
-/** the global playlist object */
-extern struct playlist g_playlist;
-
-void
-playlist_global_init(void);
-
-void
-playlist_global_finish(void);
-
-void
-playlist_init(struct playlist *playlist);
-
-void
-playlist_finish(struct playlist *playlist);
-
-void
-playlist_tag_changed(struct playlist *playlist);
-
-/**
- * Returns the "queue" object of the global playlist instance.
- */
-static inline const struct queue *
-playlist_get_queue(const struct playlist *playlist)
-{
- return &playlist->queue;
-}
-
-void
-playlist_clear(struct playlist *playlist, struct player_control *pc);
-
-/**
- * Appends a local file (outside the music database) to the playlist.
- *
- * Note: the caller is responsible for checking permissions.
- */
-enum playlist_result
-playlist_append_file(struct playlist *playlist, struct player_control *pc,
- const char *path_fs, unsigned *added_id);
-
-enum playlist_result
-playlist_append_uri(struct playlist *playlist, struct player_control *pc,
- const char *file, unsigned *added_id);
-
-enum playlist_result
-playlist_append_song(struct playlist *playlist, struct player_control *pc,
- struct song *song, unsigned *added_id);
-
-enum playlist_result
-playlist_delete(struct playlist *playlist, struct player_control *pc,
- unsigned song);
-
-/**
- * Deletes a range of songs from the playlist.
- *
- * @param start the position of the first song to delete
- * @param end the position after the last song to delete
- */
-enum playlist_result
-playlist_delete_range(struct playlist *playlist, struct player_control *pc,
- unsigned start, unsigned end);
-
-enum playlist_result
-playlist_delete_id(struct playlist *playlist, struct player_control *pc,
- unsigned song);
-
-void
-playlist_stop(struct playlist *playlist, struct player_control *pc);
-
-enum playlist_result
-playlist_play(struct playlist *playlist, struct player_control *pc,
- int song);
-
-enum playlist_result
-playlist_play_id(struct playlist *playlist, struct player_control *pc,
- int song);
-
-void
-playlist_next(struct playlist *playlist, struct player_control *pc);
-
-void
-playlist_sync(struct playlist *playlist, struct player_control *pc);
-
-void
-playlist_previous(struct playlist *playlist, struct player_control *pc);
-
-void
-playlist_shuffle(struct playlist *playlist, struct player_control *pc,
- unsigned start, unsigned end);
-
-void
-playlist_delete_song(struct playlist *playlist, struct player_control *pc,
- const struct song *song);
-
-enum playlist_result
-playlist_move_range(struct playlist *playlist, struct player_control *pc,
- unsigned start, unsigned end, int to);
-
-enum playlist_result
-playlist_move_id(struct playlist *playlist, struct player_control *pc,
- unsigned id, int to);
-
-enum playlist_result
-playlist_swap_songs(struct playlist *playlist, struct player_control *pc,
- unsigned song1, unsigned song2);
-
-enum playlist_result
-playlist_swap_songs_id(struct playlist *playlist, struct player_control *pc,
- unsigned id1, unsigned id2);
-
-enum playlist_result
-playlist_set_priority(struct playlist *playlist, struct player_control *pc,
- unsigned start_position, unsigned end_position,
- uint8_t priority);
-
-enum playlist_result
-playlist_set_priority_id(struct playlist *playlist, struct player_control *pc,
- unsigned song_id, uint8_t priority);
-
-bool
-playlist_get_repeat(const struct playlist *playlist);
-
-void
-playlist_set_repeat(struct playlist *playlist, struct player_control *pc,
- bool status);
-
-bool
-playlist_get_random(const struct playlist *playlist);
-
-void
-playlist_set_random(struct playlist *playlist, struct player_control *pc,
- bool status);
-
-bool
-playlist_get_single(const struct playlist *playlist);
-
-void
-playlist_set_single(struct playlist *playlist, struct player_control *pc,
- bool status);
-
-bool
-playlist_get_consume(const struct playlist *playlist);
-
-void
-playlist_set_consume(struct playlist *playlist, bool status);
-
-int
-playlist_get_current_song(const struct playlist *playlist);
-
-int
-playlist_get_next_song(const struct playlist *playlist);
-
-unsigned
-playlist_get_song_id(const struct playlist *playlist, unsigned song);
-
-int
-playlist_get_length(const struct playlist *playlist);
-
-unsigned long
-playlist_get_version(const struct playlist *playlist);
-
-enum playlist_result
-playlist_seek_song(struct playlist *playlist, struct player_control *pc,
- unsigned song, float seek_time);
-
-enum playlist_result
-playlist_seek_song_id(struct playlist *playlist, struct player_control *pc,
- unsigned id, float seek_time);
-
-/**
- * Seek within the current song. Fails if MPD is not currently
- * playing.
- *
- * @param time the time in seconds
- * @param relative if true, then the specified time is relative to the
- * current position
- */
-enum playlist_result
-playlist_seek_current(struct playlist *playlist, struct player_control *pc,
- float seek_time, bool relative);
-
-void
-playlist_increment_version_all(struct playlist *playlist);
-
-#endif
diff --git a/src/playlist/AsxPlaylistPlugin.cxx b/src/playlist/AsxPlaylistPlugin.cxx
new file mode 100644
index 000000000..be94f81ff
--- /dev/null
+++ b/src/playlist/AsxPlaylistPlugin.cxx
@@ -0,0 +1,283 @@
+/*
+ * 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 "AsxPlaylistPlugin.hxx"
+#include "MemoryPlaylistProvider.hxx"
+#include "input_stream.h"
+#include "Song.hxx"
+#include "Tag.hxx"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "asx"
+
+/**
+ * This is the state object for the GLib XML parser.
+ */
+struct AsxParser {
+ /**
+ * The list of songs (in reverse order because that's faster
+ * while adding).
+ */
+ std::forward_list<SongPointer> songs;
+
+ /**
+ * The current position in the XML file.
+ */
+ enum {
+ ROOT, ENTRY,
+ } state;
+
+ /**
+ * The current tag within the "entry" element. This is only
+ * valid if state==ENTRY. TAG_NUM_OF_ITEM_TYPES means there
+ * is no (known) tag.
+ */
+ enum tag_type tag;
+
+ /**
+ * The current song. It is allocated after the "location"
+ * element.
+ */
+ Song *song;
+
+ 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] != NULL; ++i)
+ if (g_ascii_strcasecmp(attribute_names[i], name) == 0)
+ return attribute_values[i];
+
+ return NULL;
+}
+
+static void
+asx_start_element(G_GNUC_UNUSED GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data, G_GNUC_UNUSED GError **error)
+{
+ AsxParser *parser = (AsxParser *)user_data;
+
+ switch (parser->state) {
+ case AsxParser::ROOT:
+ if (g_ascii_strcasecmp(element_name, "entry") == 0) {
+ parser->state = AsxParser::ENTRY;
+ parser->song = Song::NewRemote("asx:");
+ parser->tag = TAG_NUM_OF_ITEM_TYPES;
+ }
+
+ break;
+
+ case AsxParser::ENTRY:
+ if (g_ascii_strcasecmp(element_name, "ref") == 0) {
+ const gchar *href = get_attribute(attribute_names,
+ attribute_values,
+ "href");
+ if (href != NULL) {
+ /* 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 != NULL) {
+ song->tag = parser->song->tag;
+ parser->song->tag = NULL;
+ parser->song->Free();
+ }
+
+ parser->song = song;
+ }
+ } else if (g_ascii_strcasecmp(element_name, "author") == 0)
+ /* is that correct? or should it be COMPOSER
+ or PERFORMER? */
+ parser->tag = TAG_ARTIST;
+ else if (g_ascii_strcasecmp(element_name, "title") == 0)
+ parser->tag = TAG_TITLE;
+
+ break;
+ }
+}
+
+static void
+asx_end_element(G_GNUC_UNUSED GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data, G_GNUC_UNUSED GError **error)
+{
+ AsxParser *parser = (AsxParser *)user_data;
+
+ switch (parser->state) {
+ case AsxParser::ROOT:
+ break;
+
+ case AsxParser::ENTRY:
+ if (g_ascii_strcasecmp(element_name, "entry") == 0) {
+ if (strcmp(parser->song->uri, "asx:") != 0)
+ parser->songs.emplace_front(parser->song);
+ else
+ parser->song->Free();
+
+ parser->state = AsxParser::ROOT;
+ } else
+ parser->tag = TAG_NUM_OF_ITEM_TYPES;
+
+ break;
+ }
+}
+
+static void
+asx_text(G_GNUC_UNUSED GMarkupParseContext *context,
+ const gchar *text, gsize text_len,
+ gpointer user_data, G_GNUC_UNUSED GError **error)
+{
+ AsxParser *parser = (AsxParser *)user_data;
+
+ switch (parser->state) {
+ case AsxParser::ROOT:
+ break;
+
+ case AsxParser::ENTRY:
+ if (parser->tag != TAG_NUM_OF_ITEM_TYPES) {
+ if (parser->song->tag == NULL)
+ parser->song->tag = new Tag();
+ parser->song->tag->AddItem(parser->tag,
+ text, text_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
+ *
+ */
+
+static struct playlist_provider *
+asx_open_stream(struct input_stream *is)
+{
+ AsxParser parser;
+ GMarkupParseContext *context;
+ char buffer[1024];
+ size_t nbytes;
+ bool success;
+ GError *error = NULL;
+
+ /* 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 = input_stream_lock_read(is, buffer, sizeof(buffer),
+ &error);
+ if (nbytes == 0) {
+ if (error != NULL) {
+ g_markup_parse_context_free(context);
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return NULL;
+ }
+
+ break;
+ }
+
+ success = g_markup_parse_context_parse(context, buffer, nbytes,
+ &error);
+ if (!success) {
+ g_warning("XML parser failed: %s", error->message);
+ g_error_free(error);
+ g_markup_parse_context_free(context);
+ return NULL;
+ }
+ }
+
+ success = g_markup_parse_context_end_parse(context, &error);
+ if (!success) {
+ g_warning("XML parser failed: %s", error->message);
+ g_error_free(error);
+ g_markup_parse_context_free(context);
+ return NULL;
+ }
+
+ parser.songs.reverse();
+ MemoryPlaylistProvider *playlist =
+ new MemoryPlaylistProvider(std::move(parser.songs));
+
+ g_markup_parse_context_free(context);
+
+ return playlist;
+}
+
+static const char *const asx_suffixes[] = {
+ "asx",
+ NULL
+};
+
+static const char *const asx_mime_types[] = {
+ "video/x-ms-asf",
+ NULL
+};
+
+const struct playlist_plugin asx_playlist_plugin = {
+ "asx",
+
+ nullptr,
+ nullptr,
+ nullptr,
+ asx_open_stream,
+ nullptr,
+ nullptr,
+
+ nullptr,
+ asx_suffixes,
+ asx_mime_types,
+};
diff --git a/src/playlist/AsxPlaylistPlugin.hxx b/src/playlist/AsxPlaylistPlugin.hxx
new file mode 100644
index 000000000..240c1824a
--- /dev/null
+++ b/src/playlist/AsxPlaylistPlugin.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_ASX_PLAYLIST_PLUGIN_HXX
+#define MPD_ASX_PLAYLIST_PLUGIN_HXX
+
+extern const struct playlist_plugin asx_playlist_plugin;
+
+#endif
diff --git a/src/playlist/CuePlaylistPlugin.cxx b/src/playlist/CuePlaylistPlugin.cxx
new file mode 100644
index 000000000..fd9c2de09
--- /dev/null
+++ b/src/playlist/CuePlaylistPlugin.cxx
@@ -0,0 +1,110 @@
+/*
+ * 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 "CuePlaylistPlugin.hxx"
+#include "PlaylistPlugin.hxx"
+#include "Tag.hxx"
+#include "Song.hxx"
+#include "input_stream.h"
+#include "cue/CueParser.hxx"
+#include "TextInputStream.hxx"
+
+#include <glib.h>
+#include <assert.h>
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "cue"
+
+struct CuePlaylist {
+ struct playlist_provider base;
+
+ struct input_stream *is;
+ TextInputStream tis;
+ CueParser parser;
+
+ CuePlaylist(struct input_stream *_is)
+ :is(_is), tis(is) {
+ playlist_provider_init(&base, &cue_playlist_plugin);
+ }
+
+ ~CuePlaylist() {
+ }
+};
+
+static struct playlist_provider *
+cue_playlist_open_stream(struct input_stream *is)
+{
+ CuePlaylist *playlist = new CuePlaylist(is);
+ return &playlist->base;
+}
+
+static void
+cue_playlist_close(struct playlist_provider *_playlist)
+{
+ CuePlaylist *playlist = (CuePlaylist *)_playlist;
+ delete playlist;
+}
+
+static Song *
+cue_playlist_read(struct playlist_provider *_playlist)
+{
+ CuePlaylist *playlist = (CuePlaylist *)_playlist;
+
+ Song *song = playlist->parser.Get();
+ if (song != NULL)
+ return song;
+
+ std::string line;
+ while (playlist->tis.ReadLine(line)) {
+ playlist->parser.Feed(line.c_str());
+ song = playlist->parser.Get();
+ if (song != NULL)
+ return song;
+ }
+
+ playlist->parser.Finish();
+ return playlist->parser.Get();
+}
+
+static const char *const cue_playlist_suffixes[] = {
+ "cue",
+ NULL
+};
+
+static const char *const cue_playlist_mime_types[] = {
+ "application/x-cue",
+ NULL
+};
+
+const struct playlist_plugin cue_playlist_plugin = {
+ "cue",
+
+ nullptr,
+ nullptr,
+ nullptr,
+ cue_playlist_open_stream,
+ cue_playlist_close,
+ cue_playlist_read,
+
+ nullptr,
+ cue_playlist_suffixes,
+ cue_playlist_mime_types,
+};
diff --git a/src/playlist/CuePlaylistPlugin.hxx b/src/playlist/CuePlaylistPlugin.hxx
new file mode 100644
index 000000000..cf5e3a8f0
--- /dev/null
+++ b/src/playlist/CuePlaylistPlugin.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_CUE_PLAYLIST_PLUGIN_HXX
+#define MPD_CUE_PLAYLIST_PLUGIN_HXX
+
+extern const struct playlist_plugin cue_playlist_plugin;
+
+#endif
diff --git a/src/playlist/DespotifyPlaylistPlugin.cxx b/src/playlist/DespotifyPlaylistPlugin.cxx
new file mode 100644
index 000000000..99724292e
--- /dev/null
+++ b/src/playlist/DespotifyPlaylistPlugin.cxx
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2011-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 "DespotifyPlaylistPlugin.hxx"
+#include "DespotifyUtils.hxx"
+#include "MemoryPlaylistProvider.hxx"
+#include "Tag.hxx"
+#include "Song.hxx"
+
+extern "C" {
+#include <despotify.h>
+}
+
+#include <glib.h>
+
+#include <string.h>
+#include <stdlib.h>
+
+static void
+add_song(std::forward_list<SongPointer> &songs, struct ds_track *track)
+{
+ const char *dsp_scheme = despotify_playlist_plugin.schemes[0];
+ Song *song;
+ char uri[128];
+ char *ds_uri;
+
+ /* Create a spt://... URI for MPD */
+ g_snprintf(uri, sizeof(uri), "%s://", dsp_scheme);
+ ds_uri = uri + strlen(dsp_scheme) + 3;
+
+ if (despotify_track_to_uri(track, ds_uri) != ds_uri) {
+ /* Should never really fail, but let's be sure */
+ g_debug("Can't add track %s\n", track->title);
+ return;
+ }
+
+ song = Song::NewRemote(uri);
+ song->tag = mpd_despotify_tag_from_track(track);
+
+ songs.emplace_front(song);
+}
+
+static bool
+parse_track(struct despotify_session *session,
+ std::forward_list<SongPointer> &songs,
+ struct ds_link *link)
+{
+ struct ds_track *track = despotify_link_get_track(session, link);
+ if (track == nullptr)
+ return false;
+
+ add_song(songs, track);
+ return true;
+}
+
+static bool
+parse_playlist(struct despotify_session *session,
+ std::forward_list<SongPointer> &songs,
+ struct ds_link *link)
+{
+ ds_playlist *playlist = despotify_link_get_playlist(session, link);
+ if (playlist == nullptr)
+ return false;
+
+ for (ds_track *track = playlist->tracks; track != nullptr;
+ track = track->next)
+ add_song(songs, track);
+
+ return true;
+}
+
+static struct playlist_provider *
+despotify_playlist_open_uri(const char *url,
+ gcc_unused Mutex &mutex, gcc_unused Cond &cond)
+{
+ despotify_session *session = mpd_despotify_get_session();
+ if (session == nullptr)
+ return nullptr;
+
+ /* Get link without spt:// */
+ ds_link *link =
+ despotify_link_from_uri(url + strlen(despotify_playlist_plugin.schemes[0]) + 3);
+ if (link == nullptr) {
+ g_debug("Can't find %s\n", url);
+ return nullptr;
+ }
+
+ std::forward_list<SongPointer> songs;
+
+ bool parse_result;
+ switch (link->type) {
+ case LINK_TYPE_TRACK:
+ parse_result = parse_track(session, songs, link);
+ break;
+ case LINK_TYPE_PLAYLIST:
+ parse_result = parse_playlist(session, songs, link);
+ break;
+ default:
+ parse_result = false;
+ break;
+ }
+
+ despotify_free_link(link);
+ if (!parse_result)
+ return nullptr;
+
+ songs.reverse();
+ return new MemoryPlaylistProvider(std::move(songs));
+}
+
+static const char *const despotify_schemes[] = {
+ "spt",
+ nullptr
+};
+
+const struct playlist_plugin despotify_playlist_plugin = {
+ "despotify",
+
+ nullptr,
+ nullptr,
+ despotify_playlist_open_uri,
+ nullptr,
+ nullptr,
+ nullptr,
+
+ despotify_schemes,
+ nullptr,
+ nullptr,
+};
diff --git a/src/playlist/DespotifyPlaylistPlugin.hxx b/src/playlist/DespotifyPlaylistPlugin.hxx
new file mode 100644
index 000000000..c1e5b7f39
--- /dev/null
+++ b/src/playlist/DespotifyPlaylistPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2011-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_PLAYLIST_DESPOTIFY_PLAYLIST_PLUGIN_HXX
+#define MPD_PLAYLIST_DESPOTIFY_PLAYLIST_PLUGIN_HXX
+
+extern const struct playlist_plugin despotify_playlist_plugin;
+
+#endif
diff --git a/src/playlist/EmbeddedCuePlaylistPlugin.cxx b/src/playlist/EmbeddedCuePlaylistPlugin.cxx
new file mode 100644
index 000000000..3d69ed72b
--- /dev/null
+++ b/src/playlist/EmbeddedCuePlaylistPlugin.cxx
@@ -0,0 +1,187 @@
+/*
+ * 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
+ *
+ * Playlist plugin that reads embedded cue sheets from the "CUESHEET"
+ * tag of a music file.
+ */
+
+#include "config.h"
+#include "EmbeddedCuePlaylistPlugin.hxx"
+#include "PlaylistPlugin.hxx"
+#include "Tag.hxx"
+#include "TagHandler.hxx"
+#include "TagId3.hxx"
+#include "ApeTag.hxx"
+#include "Song.hxx"
+#include "TagFile.hxx"
+#include "cue/CueParser.hxx"
+
+#include <glib.h>
+#include <assert.h>
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "cue"
+
+struct embcue_playlist {
+ struct playlist_provider base;
+
+ /**
+ * This is an override for the CUE's "FILE". An embedded CUE
+ * sheet must always point to the song file it is contained
+ * in.
+ */
+ char *filename;
+
+ /**
+ * The value of the file's "CUESHEET" tag.
+ */
+ char *cuesheet;
+
+ /**
+ * The offset of the next line within "cuesheet".
+ */
+ char *next;
+
+ CueParser *parser;
+};
+
+static void
+embcue_tag_pair(const char *name, const char *value, void *ctx)
+{
+ struct embcue_playlist *playlist = (struct embcue_playlist *)ctx;
+
+ if (playlist->cuesheet == NULL &&
+ g_ascii_strcasecmp(name, "cuesheet") == 0)
+ playlist->cuesheet = g_strdup(value);
+}
+
+static const struct tag_handler embcue_tag_handler = {
+ nullptr,
+ nullptr,
+ embcue_tag_pair,
+};
+
+static struct playlist_provider *
+embcue_playlist_open_uri(const char *uri,
+ gcc_unused Mutex &mutex,
+ gcc_unused Cond &cond)
+{
+ if (!g_path_is_absolute(uri))
+ /* only local files supported */
+ return NULL;
+
+ struct embcue_playlist *playlist = g_new(struct embcue_playlist, 1);
+ playlist_provider_init(&playlist->base, &embcue_playlist_plugin);
+ playlist->cuesheet = NULL;
+
+ tag_file_scan(uri, &embcue_tag_handler, playlist);
+ if (playlist->cuesheet == NULL) {
+ tag_ape_scan2(uri, &embcue_tag_handler, playlist);
+ if (playlist->cuesheet == NULL)
+ tag_id3_scan(uri, &embcue_tag_handler, playlist);
+ }
+
+ if (playlist->cuesheet == NULL) {
+ /* no "CUESHEET" tag found */
+ g_free(playlist);
+ return NULL;
+ }
+
+ playlist->filename = g_path_get_basename(uri);
+
+ playlist->next = playlist->cuesheet;
+ playlist->parser = new CueParser();
+
+ return &playlist->base;
+}
+
+static void
+embcue_playlist_close(struct playlist_provider *_playlist)
+{
+ struct embcue_playlist *playlist = (struct embcue_playlist *)_playlist;
+
+ delete playlist->parser;
+ g_free(playlist->cuesheet);
+ g_free(playlist->filename);
+ g_free(playlist);
+}
+
+static Song *
+embcue_playlist_read(struct playlist_provider *_playlist)
+{
+ struct embcue_playlist *playlist = (struct embcue_playlist *)_playlist;
+
+ Song *song = playlist->parser->Get();
+ if (song != NULL)
+ return song;
+
+ while (*playlist->next != 0) {
+ const char *line = playlist->next;
+ char *eol = strpbrk(playlist->next, "\r\n");
+ if (eol != NULL) {
+ /* null-terminate the line */
+ *eol = 0;
+ playlist->next = eol + 1;
+ } else
+ /* last line; put the "next" pointer to the
+ end of the buffer */
+ playlist->next += strlen(line);
+
+ playlist->parser->Feed(line);
+ song = playlist->parser->Get();
+ if (song != NULL)
+ return song->ReplaceURI(playlist->filename);
+ }
+
+ playlist->parser->Finish();
+ song = playlist->parser->Get();
+ if (song != NULL)
+ song = song->ReplaceURI(playlist->filename);
+ return song;
+}
+
+static const char *const embcue_playlist_suffixes[] = {
+ /* a few codecs that are known to be supported; there are
+ probably many more */
+ "flac",
+ "mp3", "mp2",
+ "mp4", "mp4a", "m4b",
+ "ape",
+ "wv",
+ "ogg", "oga",
+ NULL
+};
+
+const struct playlist_plugin embcue_playlist_plugin = {
+ "cue",
+
+ nullptr,
+ nullptr,
+ embcue_playlist_open_uri,
+ nullptr,
+ embcue_playlist_close,
+ embcue_playlist_read,
+
+ embcue_playlist_suffixes,
+ nullptr,
+ nullptr,
+};
diff --git a/src/playlist/EmbeddedCuePlaylistPlugin.hxx b/src/playlist/EmbeddedCuePlaylistPlugin.hxx
new file mode 100644
index 000000000..e306730f4
--- /dev/null
+++ b/src/playlist/EmbeddedCuePlaylistPlugin.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_EMBCUE_PLAYLIST_PLUGIN_HXX
+#define MPD_EMBCUE_PLAYLIST_PLUGIN_HXX
+
+extern const struct playlist_plugin embcue_playlist_plugin;
+
+#endif
diff --git a/src/playlist/ExtM3uPlaylistPlugin.cxx b/src/playlist/ExtM3uPlaylistPlugin.cxx
new file mode 100644
index 000000000..fc79ba26b
--- /dev/null
+++ b/src/playlist/ExtM3uPlaylistPlugin.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 "ExtM3uPlaylistPlugin.hxx"
+#include "PlaylistPlugin.hxx"
+#include "Song.hxx"
+#include "Tag.hxx"
+#include "util/StringUtil.hxx"
+#include "TextInputStream.hxx"
+
+#include <glib.h>
+
+#include <string.h>
+#include <stdlib.h>
+
+struct ExtM3uPlaylist {
+ struct playlist_provider base;
+
+ TextInputStream *tis;
+};
+
+static struct playlist_provider *
+extm3u_open_stream(struct input_stream *is)
+{
+ ExtM3uPlaylist *playlist = g_new(ExtM3uPlaylist, 1);
+ playlist->tis = new TextInputStream(is);
+
+ std::string line;
+ if (!playlist->tis->ReadLine(line)
+ || strcmp(line.c_str(), "#EXTM3U") != 0) {
+ /* no EXTM3U header: fall back to the plain m3u
+ plugin */
+ delete playlist->tis;
+ g_free(playlist);
+ return NULL;
+ }
+
+ playlist_provider_init(&playlist->base, &extm3u_playlist_plugin);
+ return &playlist->base;
+}
+
+static void
+extm3u_close(struct playlist_provider *_playlist)
+{
+ ExtM3uPlaylist *playlist = (ExtM3uPlaylist *)_playlist;
+
+ delete playlist->tis;
+ g_free(playlist);
+}
+
+/**
+ * Parse a EXTINF line.
+ *
+ * @param line the rest of the input line after the colon
+ */
+static Tag *
+extm3u_parse_tag(const char *line)
+{
+ long duration;
+ char *endptr;
+ const char *name;
+ Tag *tag;
+
+ duration = strtol(line, &endptr, 10);
+ if (endptr[0] != ',')
+ /* malformed line */
+ return NULL;
+
+ if (duration < 0)
+ /* 0 means unknown duration */
+ duration = 0;
+
+ name = strchug_fast_c(endptr + 1);
+ if (*name == 0 && duration == 0)
+ /* no information available; don't allocate a tag
+ object */
+ return NULL;
+
+ tag = new Tag();
+ tag->time = 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);
+
+ return tag;
+}
+
+static Song *
+extm3u_read(struct playlist_provider *_playlist)
+{
+ ExtM3uPlaylist *playlist = (ExtM3uPlaylist *)_playlist;
+ Tag *tag = NULL;
+ std::string line;
+ const char *line_s;
+ Song *song;
+
+ do {
+ if (!playlist->tis->ReadLine(line)) {
+ delete tag;
+ return NULL;
+ }
+
+ line_s = line.c_str();
+
+ if (g_str_has_prefix(line_s, "#EXTINF:")) {
+ delete tag;
+ tag = extm3u_parse_tag(line_s + 8);
+ continue;
+ }
+
+ while (*line_s != 0 && g_ascii_isspace(*line_s))
+ ++line_s;
+ } while (line_s[0] == '#' || *line_s == 0);
+
+ song = Song::NewRemote(line_s);
+ song->tag = tag;
+ return song;
+}
+
+static const char *const extm3u_suffixes[] = {
+ "m3u",
+ NULL
+};
+
+static const char *const extm3u_mime_types[] = {
+ "audio/x-mpegurl",
+ NULL
+};
+
+const struct playlist_plugin extm3u_playlist_plugin = {
+ "extm3u",
+
+ nullptr,
+ nullptr,
+ nullptr,
+ extm3u_open_stream,
+ extm3u_close,
+ extm3u_read,
+
+ nullptr,
+ extm3u_suffixes,
+ extm3u_mime_types,
+};
diff --git a/src/playlist/ExtM3uPlaylistPlugin.hxx b/src/playlist/ExtM3uPlaylistPlugin.hxx
new file mode 100644
index 000000000..844fba15c
--- /dev/null
+++ b/src/playlist/ExtM3uPlaylistPlugin.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_EXTM3U_PLAYLIST_PLUGIN_HXX
+#define MPD_EXTM3U_PLAYLIST_PLUGIN_HXX
+
+extern const struct playlist_plugin extm3u_playlist_plugin;
+
+#endif
diff --git a/src/playlist/LastFMPlaylistPlugin.cxx b/src/playlist/LastFMPlaylistPlugin.cxx
new file mode 100644
index 000000000..34abaeb5f
--- /dev/null
+++ b/src/playlist/LastFMPlaylistPlugin.cxx
@@ -0,0 +1,297 @@
+/*
+ * 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 "LastFMPlaylistPlugin.hxx"
+#include "PlaylistPlugin.hxx"
+#include "PlaylistRegistry.hxx"
+#include "conf.h"
+#include "Song.hxx"
+#include "input_stream.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+
+struct lastfm_playlist {
+ struct playlist_provider base;
+
+ struct input_stream *is;
+
+ struct playlist_provider *xspf;
+};
+
+static struct {
+ char *user;
+ char *md5;
+} lastfm_config;
+
+static bool
+lastfm_init(const config_param &param)
+{
+ const char *user = param.GetBlockValue("user");
+ const char *passwd = param.GetBlockValue("password");
+
+ if (user == NULL || passwd == NULL) {
+ g_debug("disabling the last.fm playlist plugin "
+ "because account is not configured");
+ return false;
+ }
+
+ lastfm_config.user = g_uri_escape_string(user, NULL, false);
+
+ if (strlen(passwd) != 32)
+ lastfm_config.md5 = g_compute_checksum_for_string(G_CHECKSUM_MD5,
+ passwd, strlen(passwd));
+ else
+ lastfm_config.md5 = g_strdup(passwd);
+
+ return true;
+}
+
+static void
+lastfm_finish(void)
+{
+ g_free(lastfm_config.user);
+ g_free(lastfm_config.md5);
+}
+
+/**
+ * Simple data fetcher.
+ * @param url path or url of data to fetch.
+ * @return data fetched, or NULL on error. Must be freed with g_free.
+ */
+static char *
+lastfm_get(const char *url, Mutex &mutex, Cond &cond)
+{
+ struct input_stream *input_stream;
+ GError *error = NULL;
+ char buffer[4096];
+ size_t length = 0, nbytes;
+
+ input_stream = input_stream_open(url, mutex, cond, &error);
+ if (input_stream == NULL) {
+ if (error != NULL) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ }
+
+ return NULL;
+ }
+
+ mutex.lock();
+
+ input_stream_wait_ready(input_stream);
+
+ do {
+ nbytes = input_stream_read(input_stream, buffer + length,
+ sizeof(buffer) - length, &error);
+ if (nbytes == 0) {
+ if (error != NULL) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ }
+
+ if (input_stream_eof(input_stream))
+ break;
+
+ /* I/O error */
+ mutex.unlock();
+ input_stream_close(input_stream);
+ return NULL;
+ }
+
+ length += nbytes;
+ } while (length < sizeof(buffer));
+
+ mutex.unlock();
+
+ input_stream_close(input_stream);
+ return g_strndup(buffer, length);
+}
+
+/**
+ * Ini-style value fetcher.
+ * @param response data through which to search.
+ * @param name name of value to search for.
+ * @return value for param name in param response or NULL on error. Free with g_free.
+ */
+static char *
+lastfm_find(const char *response, const char *name)
+{
+ size_t name_length = strlen(name);
+
+ while (true) {
+ const char *eol = strchr(response, '\n');
+ if (eol == NULL)
+ return NULL;
+
+ if (strncmp(response, name, name_length) == 0 &&
+ response[name_length] == '=') {
+ response += name_length + 1;
+ return g_strndup(response, eol - response);
+ }
+
+ response = eol + 1;
+ }
+}
+
+static struct playlist_provider *
+lastfm_open_uri(const char *uri, Mutex &mutex, Cond &cond)
+{
+ struct lastfm_playlist *playlist;
+ GError *error = NULL;
+ char *p, *q, *response, *session;
+
+ /* handshake */
+
+ p = g_strconcat("http://ws.audioscrobbler.com/radio/handshake.php?"
+ "version=1.1.1&platform=linux&"
+ "username=", lastfm_config.user, "&"
+ "passwordmd5=", lastfm_config.md5, "&"
+ "debug=0&partner=", NULL);
+ response = lastfm_get(p, mutex, cond);
+ g_free(p);
+ if (response == NULL)
+ return NULL;
+
+ /* extract session id from response */
+
+ session = lastfm_find(response, "session");
+ g_free(response);
+ if (session == NULL) {
+ g_warning("last.fm handshake failed");
+ return NULL;
+ }
+
+ q = g_uri_escape_string(session, NULL, false);
+ g_free(session);
+ session = q;
+
+ g_debug("session='%s'", session);
+
+ /* "adjust" last.fm radio */
+
+ if (strlen(uri) > 9) {
+ char *escaped_uri;
+
+ escaped_uri = g_uri_escape_string(uri, NULL, false);
+
+ p = g_strconcat("http://ws.audioscrobbler.com/radio/adjust.php?"
+ "session=", session, "&url=", escaped_uri, "&debug=0",
+ NULL);
+ g_free(escaped_uri);
+
+ response = lastfm_get(p, mutex, cond);
+ g_free(response);
+ g_free(p);
+
+ if (response == NULL) {
+ g_free(session);
+ return NULL;
+ }
+ }
+
+ /* create the playlist object */
+
+ playlist = g_new(struct lastfm_playlist, 1);
+ playlist_provider_init(&playlist->base, &lastfm_playlist_plugin);
+
+ /* open the last.fm playlist */
+
+ p = g_strconcat("http://ws.audioscrobbler.com/radio/xspf.php?"
+ "sk=", session, "&discovery=0&desktop=1.5.1.31879",
+ NULL);
+ g_free(session);
+
+ playlist->is = input_stream_open(p, mutex, cond, &error);
+ g_free(p);
+
+ if (playlist->is == NULL) {
+ if (error != NULL) {
+ g_warning("Failed to load XSPF playlist: %s",
+ error->message);
+ g_error_free(error);
+ } else
+ g_warning("Failed to load XSPF playlist");
+ g_free(playlist);
+ return NULL;
+ }
+
+ mutex.lock();
+
+ input_stream_wait_ready(playlist->is);
+
+ /* last.fm does not send a MIME type, we have to fake it here
+ :-( */
+ input_stream_override_mime_type(playlist->is, "application/xspf+xml");
+
+ mutex.unlock();
+
+ /* parse the XSPF playlist */
+
+ playlist->xspf = playlist_list_open_stream(playlist->is, NULL);
+ if (playlist->xspf == NULL) {
+ input_stream_close(playlist->is);
+ g_free(playlist);
+ g_warning("Failed to parse XSPF playlist");
+ return NULL;
+ }
+
+ return &playlist->base;
+}
+
+static void
+lastfm_close(struct playlist_provider *_playlist)
+{
+ struct lastfm_playlist *playlist = (struct lastfm_playlist *)_playlist;
+
+ playlist_plugin_close(playlist->xspf);
+ input_stream_close(playlist->is);
+ g_free(playlist);
+}
+
+static Song *
+lastfm_read(struct playlist_provider *_playlist)
+{
+ struct lastfm_playlist *playlist = (struct lastfm_playlist *)_playlist;
+
+ return playlist_plugin_read(playlist->xspf);
+}
+
+static const char *const lastfm_schemes[] = {
+ "lastfm",
+ NULL
+};
+
+const struct playlist_plugin lastfm_playlist_plugin = {
+ "lastfm",
+
+ lastfm_init,
+ lastfm_finish,
+ lastfm_open_uri,
+ nullptr,
+ lastfm_close,
+ lastfm_read,
+
+ lastfm_schemes,
+ nullptr,
+ nullptr,
+};
diff --git a/src/playlist/LastFMPlaylistPlugin.hxx b/src/playlist/LastFMPlaylistPlugin.hxx
new file mode 100644
index 000000000..fe0e206d8
--- /dev/null
+++ b/src/playlist/LastFMPlaylistPlugin.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_LASTFM_PLAYLIST_PLUGIN_HXX
+#define MPD_LASTFM_PLAYLIST_PLUGIN_HXX
+
+extern const struct playlist_plugin lastfm_playlist_plugin;
+
+#endif
diff --git a/src/playlist/M3uPlaylistPlugin.cxx b/src/playlist/M3uPlaylistPlugin.cxx
new file mode 100644
index 000000000..ee61baa7b
--- /dev/null
+++ b/src/playlist/M3uPlaylistPlugin.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 "M3uPlaylistPlugin.hxx"
+#include "PlaylistPlugin.hxx"
+#include "Song.hxx"
+#include "TextInputStream.hxx"
+
+#include <glib.h>
+
+struct M3uPlaylist {
+ struct playlist_provider base;
+
+ TextInputStream *tis;
+};
+
+static struct playlist_provider *
+m3u_open_stream(struct input_stream *is)
+{
+ M3uPlaylist *playlist = g_new(M3uPlaylist, 1);
+
+ playlist_provider_init(&playlist->base, &m3u_playlist_plugin);
+ playlist->tis = new TextInputStream(is);
+
+ return &playlist->base;
+}
+
+static void
+m3u_close(struct playlist_provider *_playlist)
+{
+ M3uPlaylist *playlist = (M3uPlaylist *)_playlist;
+
+ delete playlist->tis;
+ g_free(playlist);
+}
+
+static Song *
+m3u_read(struct playlist_provider *_playlist)
+{
+ M3uPlaylist *playlist = (M3uPlaylist *)_playlist;
+ std::string line;
+ const char *line_s;
+
+ do {
+ if (!playlist->tis->ReadLine(line))
+ return NULL;
+
+ line_s = line.c_str();
+
+ while (*line_s != 0 && g_ascii_isspace(*line_s))
+ ++line_s;
+ } while (line_s[0] == '#' || *line_s == 0);
+
+ return Song::NewRemote(line_s);
+}
+
+static const char *const m3u_suffixes[] = {
+ "m3u",
+ NULL
+};
+
+static const char *const m3u_mime_types[] = {
+ "audio/x-mpegurl",
+ NULL
+};
+
+const struct playlist_plugin m3u_playlist_plugin = {
+ "m3u",
+
+ nullptr,
+ nullptr,
+ nullptr,
+ m3u_open_stream,
+ m3u_close,
+ m3u_read,
+
+ nullptr,
+ m3u_suffixes,
+ m3u_mime_types,
+};
diff --git a/src/playlist/M3uPlaylistPlugin.hxx b/src/playlist/M3uPlaylistPlugin.hxx
new file mode 100644
index 000000000..a2058bb29
--- /dev/null
+++ b/src/playlist/M3uPlaylistPlugin.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_M3U_PLAYLIST_PLUGIN_HXX
+#define MPD_M3U_PLAYLIST_PLUGIN_HXX
+
+extern const struct playlist_plugin m3u_playlist_plugin;
+
+#endif
diff --git a/src/playlist/MemoryPlaylistProvider.cxx b/src/playlist/MemoryPlaylistProvider.cxx
new file mode 100644
index 000000000..c2b6d9312
--- /dev/null
+++ b/src/playlist/MemoryPlaylistProvider.cxx
@@ -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.
+ */
+
+#include "config.h"
+#include "MemoryPlaylistProvider.hxx"
+#include "Song.hxx"
+
+static void
+memory_playlist_close(struct playlist_provider *_playlist)
+{
+ MemoryPlaylistProvider *playlist = (MemoryPlaylistProvider *)_playlist;
+
+ delete playlist;
+}
+
+static Song *
+memory_playlist_read(struct playlist_provider *_playlist)
+{
+ MemoryPlaylistProvider *playlist = (MemoryPlaylistProvider *)_playlist;
+
+ return playlist->Read();
+}
+
+static constexpr struct playlist_plugin memory_playlist_plugin = {
+ nullptr,
+
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ memory_playlist_close,
+ memory_playlist_read,
+
+ nullptr,
+ nullptr,
+ nullptr,
+};
+
+MemoryPlaylistProvider::MemoryPlaylistProvider(std::forward_list<SongPointer> &&_songs)
+ :songs(std::move(_songs)) {
+ playlist_provider_init(this, &memory_playlist_plugin);
+}
+
+inline Song *
+MemoryPlaylistProvider::Read()
+{
+ if (songs.empty())
+ return NULL;
+
+ auto result = songs.front().Steal();
+ songs.pop_front();
+ return result;
+}
diff --git a/src/playlist/MemoryPlaylistProvider.hxx b/src/playlist/MemoryPlaylistProvider.hxx
new file mode 100644
index 000000000..efbc46fe1
--- /dev/null
+++ b/src/playlist/MemoryPlaylistProvider.hxx
@@ -0,0 +1,39 @@
+/*
+ * 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_MEMORY_PLAYLIST_PROVIDER_HXX
+#define MPD_MEMORY_PLAYLIST_PROVIDER_HXX
+
+#include "PlaylistPlugin.hxx"
+#include "SongPointer.hxx"
+
+#include <forward_list>
+
+struct Song;
+
+class MemoryPlaylistProvider : public playlist_provider {
+ std::forward_list<SongPointer> songs;
+
+public:
+ MemoryPlaylistProvider(std::forward_list<SongPointer> &&_songs);
+
+ Song *Read();
+};
+
+#endif
diff --git a/src/playlist/PlsPlaylistPlugin.cxx b/src/playlist/PlsPlaylistPlugin.cxx
new file mode 100644
index 000000000..fdb7db77a
--- /dev/null
+++ b/src/playlist/PlsPlaylistPlugin.cxx
@@ -0,0 +1,180 @@
+/*
+ * 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 "PlsPlaylistPlugin.hxx"
+#include "MemoryPlaylistProvider.hxx"
+#include "input_stream.h"
+#include "Song.hxx"
+#include "Tag.hxx"
+
+#include <glib.h>
+
+static void
+pls_parser(GKeyFile *keyfile, std::forward_list<SongPointer> &songs)
+{
+ gchar *key;
+ gchar *value;
+ int length;
+ GError *error = NULL;
+ int num_entries = g_key_file_get_integer(keyfile, "playlist",
+ "NumberOfEntries", &error);
+ if (error) {
+ g_debug("Invalid PLS file: '%s'", error->message);
+ g_error_free(error);
+ error = NULL;
+
+ /* Hack to work around shoutcast failure to comform to spec */
+ num_entries = g_key_file_get_integer(keyfile, "playlist",
+ "numberofentries", &error);
+ if (error) {
+ g_error_free(error);
+ error = NULL;
+ }
+ }
+
+ while (num_entries > 0) {
+ Song *song;
+ key = g_strdup_printf("File%i", num_entries);
+ value = g_key_file_get_string(keyfile, "playlist", key,
+ &error);
+ if(error) {
+ g_debug("Invalid PLS entry %s: '%s'",key, error->message);
+ g_error_free(error);
+ g_free(key);
+ return;
+ }
+ g_free(key);
+
+ song = Song::NewRemote(value);
+ g_free(value);
+
+ key = g_strdup_printf("Title%i", num_entries);
+ value = g_key_file_get_string(keyfile, "playlist", key,
+ &error);
+ g_free(key);
+ if(error == NULL && value){
+ if (song->tag == NULL)
+ song->tag = new Tag();
+ song->tag->AddItem(TAG_TITLE, value);
+ }
+ /* Ignore errors? Most likely value not present */
+ if(error) g_error_free(error);
+ error = NULL;
+ g_free(value);
+
+ key = g_strdup_printf("Length%i", num_entries);
+ length = g_key_file_get_integer(keyfile, "playlist", key,
+ &error);
+ g_free(key);
+ if(error == NULL && length > 0){
+ if (song->tag == NULL)
+ song->tag = new Tag();
+ song->tag->time = length;
+ }
+ /* Ignore errors? Most likely value not present */
+ if(error) g_error_free(error);
+ error = NULL;
+
+ songs.emplace_front(song);
+ num_entries--;
+ }
+
+}
+
+static struct playlist_provider *
+pls_open_stream(struct input_stream *is)
+{
+ GError *error = NULL;
+ size_t nbytes;
+ char buffer[1024];
+ bool success;
+ GKeyFile *keyfile;
+ GString *kf_data = g_string_new("");
+
+ do {
+ nbytes = input_stream_lock_read(is, buffer, sizeof(buffer),
+ &error);
+ if (nbytes == 0) {
+ if (error != NULL) {
+ g_string_free(kf_data, TRUE);
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return NULL;
+ }
+
+ break;
+ }
+
+ kf_data = g_string_append_len(kf_data, buffer,nbytes);
+ /* Limit to 64k */
+ } while(kf_data->len < 65536);
+
+ if (kf_data->len == 0) {
+ g_warning("KeyFile parser failed: No Data");
+ g_string_free(kf_data, TRUE);
+ return NULL;
+ }
+
+ keyfile = g_key_file_new();
+ success = g_key_file_load_from_data(keyfile,
+ kf_data->str, kf_data->len,
+ G_KEY_FILE_NONE, &error);
+
+ g_string_free(kf_data, TRUE);
+
+ if (!success) {
+ g_warning("KeyFile parser failed: %s", error->message);
+ g_error_free(error);
+ g_key_file_free(keyfile);
+ return NULL;
+ }
+
+ std::forward_list<SongPointer> songs;
+ pls_parser(keyfile, songs);
+ g_key_file_free(keyfile);
+
+ songs.reverse();
+ return new MemoryPlaylistProvider(std::move(songs));
+}
+
+static const char *const pls_suffixes[] = {
+ "pls",
+ NULL
+};
+
+static const char *const pls_mime_types[] = {
+ "audio/x-scpls",
+ NULL
+};
+
+const struct playlist_plugin pls_playlist_plugin = {
+ "pls",
+
+ nullptr,
+ nullptr,
+ nullptr,
+ pls_open_stream,
+ nullptr,
+ nullptr,
+
+ nullptr,
+ pls_suffixes,
+ pls_mime_types,
+};
diff --git a/src/playlist/PlsPlaylistPlugin.hxx b/src/playlist/PlsPlaylistPlugin.hxx
new file mode 100644
index 000000000..3fafd36d0
--- /dev/null
+++ b/src/playlist/PlsPlaylistPlugin.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_PLS_PLAYLIST_PLUGIN_HXX
+#define MPD_PLS_PLAYLIST_PLUGIN_HXX
+
+extern const struct playlist_plugin pls_playlist_plugin;
+
+#endif
diff --git a/src/playlist/RssPlaylistPlugin.cxx b/src/playlist/RssPlaylistPlugin.cxx
new file mode 100644
index 000000000..e8f279bb2
--- /dev/null
+++ b/src/playlist/RssPlaylistPlugin.cxx
@@ -0,0 +1,281 @@
+/*
+ * 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 "RssPlaylistPlugin.hxx"
+#include "MemoryPlaylistProvider.hxx"
+#include "input_stream.h"
+#include "Song.hxx"
+#include "Tag.hxx"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "rss"
+
+/**
+ * This is the state object for the GLib XML parser.
+ */
+struct RssParser {
+ /**
+ * The list of songs (in reverse order because that's faster
+ * while adding).
+ */
+ std::forward_list<SongPointer> songs;
+
+ /**
+ * The current position in the XML file.
+ */
+ enum {
+ ROOT, ITEM,
+ } state;
+
+ /**
+ * The current tag within the "entry" element. This is only
+ * valid if state==ITEM. TAG_NUM_OF_ITEM_TYPES means there
+ * is no (known) tag.
+ */
+ enum tag_type tag;
+
+ /**
+ * The current song. It is allocated after the "location"
+ * element.
+ */
+ Song *song;
+
+ 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] != NULL; ++i)
+ if (g_ascii_strcasecmp(attribute_names[i], name) == 0)
+ return attribute_values[i];
+
+ return NULL;
+}
+
+static void
+rss_start_element(G_GNUC_UNUSED GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data, G_GNUC_UNUSED GError **error)
+{
+ RssParser *parser = (RssParser *)user_data;
+
+ switch (parser->state) {
+ case RssParser::ROOT:
+ if (g_ascii_strcasecmp(element_name, "item") == 0) {
+ parser->state = RssParser::ITEM;
+ parser->song = Song::NewRemote("rss:");
+ parser->tag = TAG_NUM_OF_ITEM_TYPES;
+ }
+
+ break;
+
+ case RssParser::ITEM:
+ if (g_ascii_strcasecmp(element_name, "enclosure") == 0) {
+ const gchar *href = get_attribute(attribute_names,
+ attribute_values,
+ "url");
+ if (href != NULL) {
+ /* 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 != NULL) {
+ song->tag = parser->song->tag;
+ parser->song->tag = NULL;
+ parser->song->Free();
+ }
+
+ parser->song = song;
+ }
+ } else if (g_ascii_strcasecmp(element_name, "title") == 0)
+ parser->tag = TAG_TITLE;
+ else if (g_ascii_strcasecmp(element_name, "itunes:author") == 0)
+ parser->tag = TAG_ARTIST;
+
+ break;
+ }
+}
+
+static void
+rss_end_element(G_GNUC_UNUSED GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data, G_GNUC_UNUSED GError **error)
+{
+ RssParser *parser = (RssParser *)user_data;
+
+ switch (parser->state) {
+ case RssParser::ROOT:
+ break;
+
+ case RssParser::ITEM:
+ if (g_ascii_strcasecmp(element_name, "item") == 0) {
+ if (strcmp(parser->song->uri, "rss:") != 0)
+ parser->songs.emplace_front(parser->song);
+ else
+ parser->song->Free();
+
+ parser->state = RssParser::ROOT;
+ } else
+ parser->tag = TAG_NUM_OF_ITEM_TYPES;
+
+ break;
+ }
+}
+
+static void
+rss_text(G_GNUC_UNUSED GMarkupParseContext *context,
+ const gchar *text, gsize text_len,
+ gpointer user_data, G_GNUC_UNUSED GError **error)
+{
+ RssParser *parser = (RssParser *)user_data;
+
+ switch (parser->state) {
+ case RssParser::ROOT:
+ break;
+
+ case RssParser::ITEM:
+ if (parser->tag != TAG_NUM_OF_ITEM_TYPES) {
+ if (parser->song->tag == NULL)
+ parser->song->tag = new Tag();
+ parser->song->tag->AddItem(parser->tag,
+ text, text_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
+ *
+ */
+
+static struct playlist_provider *
+rss_open_stream(struct input_stream *is)
+{
+ RssParser parser;
+ GMarkupParseContext *context;
+ char buffer[1024];
+ size_t nbytes;
+ bool success;
+ GError *error = NULL;
+
+ /* 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 = input_stream_lock_read(is, buffer, sizeof(buffer),
+ &error);
+ if (nbytes == 0) {
+ if (error != NULL) {
+ g_markup_parse_context_free(context);
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return NULL;
+ }
+
+ break;
+ }
+
+ success = g_markup_parse_context_parse(context, buffer, nbytes,
+ &error);
+ if (!success) {
+ g_warning("XML parser failed: %s", error->message);
+ g_error_free(error);
+ g_markup_parse_context_free(context);
+ return NULL;
+ }
+ }
+
+ success = g_markup_parse_context_end_parse(context, &error);
+ if (!success) {
+ g_warning("XML parser failed: %s", error->message);
+ g_error_free(error);
+ g_markup_parse_context_free(context);
+ return NULL;
+ }
+
+ parser.songs.reverse();
+ MemoryPlaylistProvider *playlist =
+ new MemoryPlaylistProvider(std::move(parser.songs));
+
+ g_markup_parse_context_free(context);
+
+ return playlist;
+}
+
+static const char *const rss_suffixes[] = {
+ "rss",
+ NULL
+};
+
+static const char *const rss_mime_types[] = {
+ "application/rss+xml",
+ "text/xml",
+ NULL
+};
+
+const struct playlist_plugin rss_playlist_plugin = {
+ "rss",
+
+ nullptr,
+ nullptr,
+ nullptr,
+ rss_open_stream,
+ nullptr,
+ nullptr,
+
+ nullptr,
+ rss_suffixes,
+ rss_mime_types,
+};
diff --git a/src/playlist/RssPlaylistPlugin.hxx b/src/playlist/RssPlaylistPlugin.hxx
new file mode 100644
index 000000000..f49f7e9cf
--- /dev/null
+++ b/src/playlist/RssPlaylistPlugin.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_RSS_PLAYLIST_PLUGIN_HXX
+#define MPD_RSS_PLAYLIST_PLUGIN_HXX
+
+extern const struct playlist_plugin rss_playlist_plugin;
+
+#endif
diff --git a/src/playlist/SoundCloudPlaylistPlugin.cxx b/src/playlist/SoundCloudPlaylistPlugin.cxx
new file mode 100644
index 000000000..ce9bd1385
--- /dev/null
+++ b/src/playlist/SoundCloudPlaylistPlugin.cxx
@@ -0,0 +1,417 @@
+/*
+ * 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 "SoundCloudPlaylistPlugin.hxx"
+#include "MemoryPlaylistProvider.hxx"
+#include "conf.h"
+#include "input_stream.h"
+#include "Song.hxx"
+#include "Tag.hxx"
+
+#include <glib.h>
+#include <yajl/yajl_parse.h>
+
+#include <string.h>
+
+static struct {
+ char *apikey;
+} soundcloud_config;
+
+static bool
+soundcloud_init(const config_param &param)
+{
+ soundcloud_config.apikey = param.DupBlockString("apikey");
+ if (soundcloud_config.apikey == NULL) {
+ g_debug("disabling the soundcloud playlist plugin "
+ "because API key is not set");
+ return false;
+ }
+
+ return true;
+}
+
+static void
+soundcloud_finish(void)
+{
+ g_free(soundcloud_config.apikey);
+}
+
+/**
+ * Construct a full soundcloud resolver URL from the given fragment.
+ * @param uri uri of a soundcloud page (or just the path)
+ * @return Constructed URL. Must be freed with g_free.
+ */
+static char *
+soundcloud_resolve(const char* uri) {
+ char *u, *ru;
+
+ if (g_str_has_prefix(uri, "http://")) {
+ u = g_strdup(uri);
+ } else if (g_str_has_prefix(uri, "soundcloud.com")) {
+ u = g_strconcat("http://", uri, NULL);
+ } else {
+ /* assume it's just a path on soundcloud.com */
+ u = g_strconcat("http://soundcloud.com/", uri, NULL);
+ }
+
+ ru = g_strconcat("http://api.soundcloud.com/resolve.json?url=",
+ u, "&client_id=", soundcloud_config.apikey, NULL);
+ g_free(u);
+
+ return ru;
+}
+
+/* YAJL parser for track data from both /tracks/ and /playlists/ JSON */
+
+enum key {
+ Duration,
+ Title,
+ Stream_URL,
+ Other,
+};
+
+const char* key_str[] = {
+ "duration",
+ "title",
+ "stream_url",
+ NULL,
+};
+
+struct parse_data {
+ int key;
+ char* stream_url;
+ long duration;
+ char* title;
+ int got_url; /* nesting level of last stream_url */
+
+ std::forward_list<SongPointer> songs;
+};
+
+static int handle_integer(void *ctx,
+ long
+#ifndef HAVE_YAJL1
+ long
+#endif
+ intval)
+{
+ struct parse_data *data = (struct parse_data *) ctx;
+
+ switch (data->key) {
+ case Duration:
+ data->duration = intval;
+ break;
+ default:
+ break;
+ }
+
+ return 1;
+}
+
+static int handle_string(void *ctx, const unsigned char* stringval,
+#ifdef HAVE_YAJL1
+ unsigned int
+#else
+ size_t
+#endif
+ stringlen)
+{
+ struct parse_data *data = (struct parse_data *) ctx;
+ const char *s = (const char *) stringval;
+
+ switch (data->key) {
+ case Title:
+ if (data->title != NULL)
+ g_free(data->title);
+ data->title = g_strndup(s, stringlen);
+ break;
+ case Stream_URL:
+ if (data->stream_url != NULL)
+ g_free(data->stream_url);
+ data->stream_url = g_strndup(s, stringlen);
+ data->got_url = 1;
+ break;
+ default:
+ break;
+ }
+
+ return 1;
+}
+
+static int handle_mapkey(void *ctx, const unsigned char* stringval,
+#ifdef HAVE_YAJL1
+ unsigned int
+#else
+ size_t
+#endif
+ stringlen)
+{
+ struct parse_data *data = (struct parse_data *) ctx;
+
+ int i;
+ data->key = Other;
+
+ for (i = 0; i < Other; ++i) {
+ if (strncmp((const char *)stringval, key_str[i], stringlen) == 0) {
+ data->key = i;
+ break;
+ }
+ }
+
+ return 1;
+}
+
+static int handle_start_map(void *ctx)
+{
+ struct parse_data *data = (struct parse_data *) ctx;
+
+ if (data->got_url > 0)
+ data->got_url++;
+
+ return 1;
+}
+
+static int handle_end_map(void *ctx)
+{
+ struct parse_data *data = (struct parse_data *) ctx;
+
+ if (data->got_url > 1) {
+ data->got_url--;
+ return 1;
+ }
+
+ if (data->got_url == 0)
+ return 1;
+
+ /* got_url == 1, track finished, make it into a song */
+ data->got_url = 0;
+
+ Song *s;
+ char *u;
+
+ u = g_strconcat(data->stream_url, "?client_id=", soundcloud_config.apikey, NULL);
+ s = Song::NewRemote(u);
+ g_free(u);
+
+ Tag *t = new Tag();
+ t->time = data->duration / 1000;
+ if (data->title != NULL)
+ t->AddItem(TAG_NAME, data->title);
+ s->tag = t;
+
+ data->songs.emplace_front(s);
+
+ return 1;
+}
+
+static yajl_callbacks parse_callbacks = {
+ NULL,
+ NULL,
+ handle_integer,
+ NULL,
+ NULL,
+ handle_string,
+ handle_start_map,
+ handle_mapkey,
+ handle_end_map,
+ NULL,
+ NULL,
+};
+
+/**
+ * Read JSON data and parse it using the given YAJL parser.
+ * @param url URL of the JSON data.
+ * @param hand YAJL parser handle.
+ * @return -1 on error, 0 on success.
+ */
+static int
+soundcloud_parse_json(const char *url, yajl_handle hand,
+ Mutex &mutex, Cond &cond)
+{
+ struct input_stream *input_stream;
+ GError *error = NULL;
+ char buffer[4096];
+ unsigned char *ubuffer = (unsigned char *)buffer;
+ size_t nbytes;
+
+ input_stream = input_stream_open(url, mutex, cond, &error);
+ if (input_stream == NULL) {
+ if (error != NULL) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ }
+ return -1;
+ }
+
+ mutex.lock();
+ input_stream_wait_ready(input_stream);
+
+ yajl_status stat;
+ int done = 0;
+
+ while (!done) {
+ nbytes = input_stream_read(input_stream, buffer, sizeof(buffer), &error);
+ if (nbytes == 0) {
+ if (error != NULL) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ }
+ if (input_stream_eof(input_stream)) {
+ done = true;
+ } else {
+ mutex.unlock();
+ input_stream_close(input_stream);
+ return -1;
+ }
+ }
+
+ if (done) {
+#ifdef HAVE_YAJL1
+ stat = yajl_parse_complete(hand);
+#else
+ stat = yajl_complete_parse(hand);
+#endif
+ } else
+ stat = yajl_parse(hand, ubuffer, nbytes);
+
+ if (stat != yajl_status_ok
+#ifdef HAVE_YAJL1
+ && stat != yajl_status_insufficient_data
+#endif
+ )
+ {
+ unsigned char *str = yajl_get_error(hand, 1, ubuffer, nbytes);
+ g_warning("%s", str);
+ yajl_free_error(hand, str);
+ break;
+ }
+ }
+
+ mutex.unlock();
+ input_stream_close(input_stream);
+
+ return 0;
+}
+
+/**
+ * Parse a soundcloud:// URL and create a playlist.
+ * @param uri A soundcloud URL. Accepted forms:
+ * soundcloud://track/<track-id>
+ * soundcloud://playlist/<playlist-id>
+ * soundcloud://url/<url or path of soundcloud page>
+ */
+
+static struct playlist_provider *
+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) {
+ g_warning("incompatible scheme for soundcloud plugin: %s", scheme);
+ g_free(s);
+ return NULL;
+ }
+
+ char *u = NULL;
+ if (strcmp(arg, "track") == 0) {
+ u = g_strconcat("http://api.soundcloud.com/tracks/",
+ rest, ".json?client_id=", soundcloud_config.apikey, NULL);
+ } else if (strcmp(arg, "playlist") == 0) {
+ u = g_strconcat("http://api.soundcloud.com/playlists/",
+ rest, ".json?client_id=", soundcloud_config.apikey, NULL);
+ } else if (strcmp(arg, "url") == 0) {
+ /* Translate to soundcloud resolver call. libcurl will automatically
+ follow the redirect to the right resource. */
+ u = soundcloud_resolve(rest);
+ }
+ g_free(s);
+
+ if (u == NULL) {
+ g_warning("unknown soundcloud URI");
+ return NULL;
+ }
+
+ yajl_handle hand;
+ struct parse_data data;
+
+ data.got_url = 0;
+ data.title = NULL;
+ data.stream_url = NULL;
+#ifdef HAVE_YAJL1
+ hand = yajl_alloc(&parse_callbacks, NULL, NULL, (void *) &data);
+#else
+ hand = yajl_alloc(&parse_callbacks, NULL, (void *) &data);
+#endif
+
+ int ret = soundcloud_parse_json(u, hand, mutex, cond);
+
+ g_free(u);
+ yajl_free(hand);
+ if (data.title != NULL)
+ g_free(data.title);
+ if (data.stream_url != NULL)
+ g_free(data.stream_url);
+
+ if (ret == -1)
+ return NULL;
+
+ data.songs.reverse();
+ return new MemoryPlaylistProvider(std::move(data.songs));
+}
+
+static const char *const soundcloud_schemes[] = {
+ "soundcloud",
+ NULL
+};
+
+const struct playlist_plugin soundcloud_playlist_plugin = {
+ "soundcloud",
+
+ soundcloud_init,
+ soundcloud_finish,
+ soundcloud_open_uri,
+ nullptr,
+ nullptr,
+ nullptr,
+
+ soundcloud_schemes,
+ nullptr,
+ nullptr,
+};
+
+
diff --git a/src/playlist/SoundCloudPlaylistPlugin.hxx b/src/playlist/SoundCloudPlaylistPlugin.hxx
new file mode 100644
index 000000000..7c121328c
--- /dev/null
+++ b/src/playlist/SoundCloudPlaylistPlugin.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_SOUNDCLOUD_PLAYLIST_PLUGIN_HXX
+#define MPD_SOUNDCLOUD_PLAYLIST_PLUGIN_HXX
+
+extern const struct playlist_plugin soundcloud_playlist_plugin;
+
+#endif
diff --git a/src/playlist/XspfPlaylistPlugin.cxx b/src/playlist/XspfPlaylistPlugin.cxx
new file mode 100644
index 000000000..cb3d19033
--- /dev/null
+++ b/src/playlist/XspfPlaylistPlugin.cxx
@@ -0,0 +1,299 @@
+/*
+ * 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 "XspfPlaylistPlugin.hxx"
+#include "MemoryPlaylistProvider.hxx"
+#include "input_stream.h"
+#include "Tag.hxx"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "xspf"
+
+/**
+ * This is the state object for the GLib XML parser.
+ */
+struct XspfParser {
+ /**
+ * The list of songs (in reverse order because that's faster
+ * while adding).
+ */
+ std::forward_list<SongPointer> songs;
+
+ /**
+ * The current position in the XML file.
+ */
+ enum {
+ ROOT, PLAYLIST, TRACKLIST, TRACK,
+ LOCATION,
+ } state;
+
+ /**
+ * The current tag within the "track" element. This is only
+ * valid if state==TRACK. TAG_NUM_OF_ITEM_TYPES means there
+ * is no (known) tag.
+ */
+ enum tag_type tag;
+
+ /**
+ * The current song. It is allocated after the "location"
+ * element.
+ */
+ Song *song;
+
+ XspfParser()
+ :state(ROOT) {}
+};
+
+static void
+xspf_start_element(G_GNUC_UNUSED GMarkupParseContext *context,
+ const gchar *element_name,
+ G_GNUC_UNUSED const gchar **attribute_names,
+ G_GNUC_UNUSED const gchar **attribute_values,
+ gpointer user_data, G_GNUC_UNUSED GError **error)
+{
+ XspfParser *parser = (XspfParser *)user_data;
+
+ switch (parser->state) {
+ case XspfParser::ROOT:
+ if (strcmp(element_name, "playlist") == 0)
+ parser->state = XspfParser::PLAYLIST;
+
+ break;
+
+ case XspfParser::PLAYLIST:
+ if (strcmp(element_name, "trackList") == 0)
+ parser->state = XspfParser::TRACKLIST;
+
+ break;
+
+ case XspfParser::TRACKLIST:
+ if (strcmp(element_name, "track") == 0) {
+ parser->state = XspfParser::TRACK;
+ parser->song = NULL;
+ parser->tag = TAG_NUM_OF_ITEM_TYPES;
+ }
+
+ break;
+
+ case XspfParser::TRACK:
+ if (strcmp(element_name, "location") == 0)
+ parser->state = XspfParser::LOCATION;
+ else if (strcmp(element_name, "title") == 0)
+ parser->tag = TAG_TITLE;
+ else if (strcmp(element_name, "creator") == 0)
+ /* TAG_COMPOSER would be more correct
+ according to the XSPF spec */
+ parser->tag = TAG_ARTIST;
+ else if (strcmp(element_name, "annotation") == 0)
+ parser->tag = TAG_COMMENT;
+ else if (strcmp(element_name, "album") == 0)
+ parser->tag = TAG_ALBUM;
+ else if (strcmp(element_name, "trackNum") == 0)
+ parser->tag = TAG_TRACK;
+
+ break;
+
+ case XspfParser::LOCATION:
+ break;
+ }
+}
+
+static void
+xspf_end_element(G_GNUC_UNUSED GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data, G_GNUC_UNUSED GError **error)
+{
+ XspfParser *parser = (XspfParser *)user_data;
+
+ switch (parser->state) {
+ case XspfParser::ROOT:
+ break;
+
+ case XspfParser::PLAYLIST:
+ if (strcmp(element_name, "playlist") == 0)
+ parser->state = XspfParser::ROOT;
+
+ break;
+
+ case XspfParser::TRACKLIST:
+ if (strcmp(element_name, "tracklist") == 0)
+ parser->state = XspfParser::PLAYLIST;
+
+ break;
+
+ case XspfParser::TRACK:
+ if (strcmp(element_name, "track") == 0) {
+ if (parser->song != NULL)
+ parser->songs.emplace_front(parser->song);
+
+ parser->state = XspfParser::TRACKLIST;
+ } else
+ parser->tag = TAG_NUM_OF_ITEM_TYPES;
+
+ break;
+
+ case XspfParser::LOCATION:
+ parser->state = XspfParser::TRACK;
+ break;
+ }
+}
+
+static void
+xspf_text(G_GNUC_UNUSED GMarkupParseContext *context,
+ const gchar *text, gsize text_len,
+ gpointer user_data, G_GNUC_UNUSED GError **error)
+{
+ XspfParser *parser = (XspfParser *)user_data;
+
+ switch (parser->state) {
+ case XspfParser::ROOT:
+ case XspfParser::PLAYLIST:
+ case XspfParser::TRACKLIST:
+ break;
+
+ case XspfParser::TRACK:
+ if (parser->song != NULL &&
+ parser->tag != TAG_NUM_OF_ITEM_TYPES) {
+ if (parser->song->tag == NULL)
+ parser->song->tag = new Tag();
+ parser->song->tag->AddItem(parser->tag, text, text_len);
+ }
+
+ break;
+
+ case XspfParser::LOCATION:
+ if (parser->song == NULL) {
+ char *uri = g_strndup(text, text_len);
+ parser->song = Song::NewRemote(uri);
+ g_free(uri);
+ }
+
+ 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 != NULL)
+ parser->song->Free();
+}
+
+/*
+ * The playlist object
+ *
+ */
+
+static struct playlist_provider *
+xspf_open_stream(struct input_stream *is)
+{
+ XspfParser parser;
+ GMarkupParseContext *context;
+ char buffer[1024];
+ size_t nbytes;
+ bool success;
+ GError *error = NULL;
+
+ /* 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 = input_stream_lock_read(is, buffer, sizeof(buffer),
+ &error);
+ if (nbytes == 0) {
+ if (error != NULL) {
+ g_markup_parse_context_free(context);
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return NULL;
+ }
+
+ break;
+ }
+
+ success = g_markup_parse_context_parse(context, buffer, nbytes,
+ &error);
+ if (!success) {
+ g_warning("XML parser failed: %s", error->message);
+ g_error_free(error);
+ g_markup_parse_context_free(context);
+ return NULL;
+ }
+ }
+
+ success = g_markup_parse_context_end_parse(context, &error);
+ if (!success) {
+ g_warning("XML parser failed: %s", error->message);
+ g_error_free(error);
+ g_markup_parse_context_free(context);
+ return NULL;
+ }
+
+ parser.songs.reverse();
+ MemoryPlaylistProvider *playlist =
+ new MemoryPlaylistProvider(std::move(parser.songs));
+
+ g_markup_parse_context_free(context);
+
+ return playlist;
+}
+
+static const char *const xspf_suffixes[] = {
+ "xspf",
+ NULL
+};
+
+static const char *const xspf_mime_types[] = {
+ "application/xspf+xml",
+ NULL
+};
+
+const struct playlist_plugin xspf_playlist_plugin = {
+ "xspf",
+
+ nullptr,
+ nullptr,
+ nullptr,
+ xspf_open_stream,
+ nullptr,
+ nullptr,
+
+ nullptr,
+ xspf_suffixes,
+ xspf_mime_types,
+};
diff --git a/src/playlist/XspfPlaylistPlugin.hxx b/src/playlist/XspfPlaylistPlugin.hxx
new file mode 100644
index 000000000..fc9bbd2c6
--- /dev/null
+++ b/src/playlist/XspfPlaylistPlugin.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_XSPF_PLAYLIST_PLUGIN_HXX
+#define MPD_XSPF_PLAYLIST_PLUGIN_HXX
+
+extern const struct playlist_plugin xspf_playlist_plugin;
+
+#endif
diff --git a/src/playlist/asx_playlist_plugin.c b/src/playlist/asx_playlist_plugin.c
deleted file mode 100644
index 298687859..000000000
--- a/src/playlist/asx_playlist_plugin.c
+++ /dev/null
@@ -1,323 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "playlist/asx_playlist_plugin.h"
-#include "playlist_plugin.h"
-#include "input_stream.h"
-#include "song.h"
-#include "tag.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "asx"
-
-/**
- * This is the state object for the GLib XML parser.
- */
-struct asx_parser {
- /**
- * The list of songs (in reverse order because that's faster
- * while adding).
- */
- GSList *songs;
-
- /**
- * The current position in the XML file.
- */
- enum {
- ROOT, ENTRY,
- } state;
-
- /**
- * The current tag within the "entry" element. This is only
- * valid if state==ENTRY. TAG_NUM_OF_ITEM_TYPES means there
- * is no (known) tag.
- */
- enum tag_type tag;
-
- /**
- * The current song. It is allocated after the "location"
- * element.
- */
- struct song *song;
-};
-
-static const gchar *
-get_attribute(const gchar **attribute_names, const gchar **attribute_values,
- const gchar *name)
-{
- for (unsigned i = 0; attribute_names[i] != NULL; ++i)
- if (g_ascii_strcasecmp(attribute_names[i], name) == 0)
- return attribute_values[i];
-
- return NULL;
-}
-
-static void
-asx_start_element(G_GNUC_UNUSED GMarkupParseContext *context,
- const gchar *element_name,
- const gchar **attribute_names,
- const gchar **attribute_values,
- gpointer user_data, G_GNUC_UNUSED GError **error)
-{
- struct asx_parser *parser = user_data;
-
- switch (parser->state) {
- case ROOT:
- if (g_ascii_strcasecmp(element_name, "entry") == 0) {
- parser->state = ENTRY;
- parser->song = song_remote_new("asx:");
- parser->tag = TAG_NUM_OF_ITEM_TYPES;
- }
-
- break;
-
- case ENTRY:
- if (g_ascii_strcasecmp(element_name, "ref") == 0) {
- const gchar *href = get_attribute(attribute_names,
- attribute_values,
- "href");
- if (href != NULL) {
- /* create new song object, and copy
- the existing tag over; we cannot
- replace the existing song's URI,
- because that attribute is
- immutable */
- struct song *song = song_remote_new(href);
-
- if (parser->song != NULL) {
- song->tag = parser->song->tag;
- parser->song->tag = NULL;
- song_free(parser->song);
- }
-
- parser->song = song;
- }
- } else if (g_ascii_strcasecmp(element_name, "author") == 0)
- /* is that correct? or should it be COMPOSER
- or PERFORMER? */
- parser->tag = TAG_ARTIST;
- else if (g_ascii_strcasecmp(element_name, "title") == 0)
- parser->tag = TAG_TITLE;
-
- break;
- }
-}
-
-static void
-asx_end_element(G_GNUC_UNUSED GMarkupParseContext *context,
- const gchar *element_name,
- gpointer user_data, G_GNUC_UNUSED GError **error)
-{
- struct asx_parser *parser = user_data;
-
- switch (parser->state) {
- case ROOT:
- break;
-
- case ENTRY:
- if (g_ascii_strcasecmp(element_name, "entry") == 0) {
- if (strcmp(parser->song->uri, "asx:") != 0)
- parser->songs = g_slist_prepend(parser->songs,
- parser->song);
- else
- song_free(parser->song);
-
- parser->state = ROOT;
- } else
- parser->tag = TAG_NUM_OF_ITEM_TYPES;
-
- break;
- }
-}
-
-static void
-asx_text(G_GNUC_UNUSED GMarkupParseContext *context,
- const gchar *text, gsize text_len,
- gpointer user_data, G_GNUC_UNUSED GError **error)
-{
- struct asx_parser *parser = user_data;
-
- switch (parser->state) {
- case ROOT:
- break;
-
- case ENTRY:
- if (parser->tag != TAG_NUM_OF_ITEM_TYPES) {
- if (parser->song->tag == NULL)
- parser->song->tag = tag_new();
- tag_add_item_n(parser->song->tag, parser->tag,
- text, text_len);
- }
-
- break;
- }
-}
-
-static const GMarkupParser asx_parser = {
- .start_element = asx_start_element,
- .end_element = asx_end_element,
- .text = asx_text,
-};
-
-static void
-song_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
-{
- struct song *song = data;
-
- song_free(song);
-}
-
-static void
-asx_parser_destroy(gpointer data)
-{
- struct asx_parser *parser = data;
-
- if (parser->state >= ENTRY)
- song_free(parser->song);
-
- g_slist_foreach(parser->songs, song_free_callback, NULL);
- g_slist_free(parser->songs);
-}
-
-/*
- * The playlist object
- *
- */
-
-struct asx_playlist {
- struct playlist_provider base;
-
- GSList *songs;
-};
-
-static struct playlist_provider *
-asx_open_stream(struct input_stream *is)
-{
- struct asx_parser parser = {
- .songs = NULL,
- .state = ROOT,
- };
- struct asx_playlist *playlist;
- GMarkupParseContext *context;
- char buffer[1024];
- size_t nbytes;
- bool success;
- GError *error = NULL;
-
- /* 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 = input_stream_lock_read(is, buffer, sizeof(buffer),
- &error);
- if (nbytes == 0) {
- if (error != NULL) {
- g_markup_parse_context_free(context);
- g_warning("%s", error->message);
- g_error_free(error);
- return NULL;
- }
-
- break;
- }
-
- success = g_markup_parse_context_parse(context, buffer, nbytes,
- &error);
- if (!success) {
- g_warning("XML parser failed: %s", error->message);
- g_error_free(error);
- g_markup_parse_context_free(context);
- return NULL;
- }
- }
-
- success = g_markup_parse_context_end_parse(context, &error);
- if (!success) {
- g_warning("XML parser failed: %s", error->message);
- g_error_free(error);
- g_markup_parse_context_free(context);
- return NULL;
- }
-
- /* create a #asx_playlist object from the parsed song list */
-
- playlist = g_new(struct asx_playlist, 1);
- playlist_provider_init(&playlist->base, &asx_playlist_plugin);
- playlist->songs = g_slist_reverse(parser.songs);
- parser.songs = NULL;
-
- g_markup_parse_context_free(context);
-
- return &playlist->base;
-}
-
-static void
-asx_close(struct playlist_provider *_playlist)
-{
- struct asx_playlist *playlist = (struct asx_playlist *)_playlist;
-
- g_slist_foreach(playlist->songs, song_free_callback, NULL);
- g_slist_free(playlist->songs);
- g_free(playlist);
-}
-
-static struct song *
-asx_read(struct playlist_provider *_playlist)
-{
- struct asx_playlist *playlist = (struct asx_playlist *)_playlist;
- struct song *song;
-
- if (playlist->songs == NULL)
- return NULL;
-
- song = playlist->songs->data;
- playlist->songs = g_slist_remove(playlist->songs, song);
-
- return song;
-}
-
-static const char *const asx_suffixes[] = {
- "asx",
- NULL
-};
-
-static const char *const asx_mime_types[] = {
- "video/x-ms-asf",
- NULL
-};
-
-const struct playlist_plugin asx_playlist_plugin = {
- .name = "asx",
-
- .open_stream = asx_open_stream,
- .close = asx_close,
- .read = asx_read,
-
- .suffixes = asx_suffixes,
- .mime_types = asx_mime_types,
-};
diff --git a/src/playlist/asx_playlist_plugin.h b/src/playlist/asx_playlist_plugin.h
deleted file mode 100644
index 6c01c1209..000000000
--- a/src/playlist/asx_playlist_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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_PLAYLIST_ASX_PLAYLIST_PLUGIN_H
-#define MPD_PLAYLIST_ASX_PLAYLIST_PLUGIN_H
-
-extern const struct playlist_plugin asx_playlist_plugin;
-
-#endif
diff --git a/src/playlist/cue_playlist_plugin.c b/src/playlist/cue_playlist_plugin.c
deleted file mode 100644
index b85de77d3..000000000
--- a/src/playlist/cue_playlist_plugin.c
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "playlist/cue_playlist_plugin.h"
-#include "playlist_plugin.h"
-#include "tag.h"
-#include "song.h"
-#include "cue/cue_parser.h"
-#include "input_stream.h"
-#include "text_input_stream.h"
-
-#include <glib.h>
-#include <assert.h>
-#include <string.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "cue"
-
-struct cue_playlist {
- struct playlist_provider base;
-
- struct input_stream *is;
- struct text_input_stream *tis;
- struct cue_parser *parser;
-};
-
-static struct playlist_provider *
-cue_playlist_open_stream(struct input_stream *is)
-{
- struct cue_playlist *playlist = g_new(struct cue_playlist, 1);
- playlist_provider_init(&playlist->base, &cue_playlist_plugin);
-
- playlist->is = is;
- playlist->tis = text_input_stream_new(is);
- playlist->parser = cue_parser_new();
-
-
- return &playlist->base;
-}
-
-static void
-cue_playlist_close(struct playlist_provider *_playlist)
-{
- struct cue_playlist *playlist = (struct cue_playlist *)_playlist;
-
- cue_parser_free(playlist->parser);
- text_input_stream_free(playlist->tis);
- g_free(playlist);
-}
-
-static struct song *
-cue_playlist_read(struct playlist_provider *_playlist)
-{
- struct cue_playlist *playlist = (struct cue_playlist *)_playlist;
-
- struct song *song = cue_parser_get(playlist->parser);
- if (song != NULL)
- return song;
-
- const char *line;
- while ((line = text_input_stream_read(playlist->tis)) != NULL) {
- cue_parser_feed(playlist->parser, line);
- song = cue_parser_get(playlist->parser);
- if (song != NULL)
- return song;
- }
-
- cue_parser_finish(playlist->parser);
- return cue_parser_get(playlist->parser);
-}
-
-static const char *const cue_playlist_suffixes[] = {
- "cue",
- NULL
-};
-
-static const char *const cue_playlist_mime_types[] = {
- "application/x-cue",
- NULL
-};
-
-const struct playlist_plugin cue_playlist_plugin = {
- .name = "cue",
-
- .open_stream = cue_playlist_open_stream,
- .close = cue_playlist_close,
- .read = cue_playlist_read,
-
- .suffixes = cue_playlist_suffixes,
- .mime_types = cue_playlist_mime_types,
-};
diff --git a/src/playlist/cue_playlist_plugin.h b/src/playlist/cue_playlist_plugin.h
deleted file mode 100644
index c02e2235a..000000000
--- a/src/playlist/cue_playlist_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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_PLAYLIST_CUE_PLAYLIST_PLUGIN_H
-#define MPD_PLAYLIST_CUE_PLAYLIST_PLUGIN_H
-
-extern const struct playlist_plugin cue_playlist_plugin;
-
-#endif
diff --git a/src/playlist/despotify_playlist_plugin.c b/src/playlist/despotify_playlist_plugin.c
deleted file mode 100644
index 30b852c73..000000000
--- a/src/playlist/despotify_playlist_plugin.c
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- * Copyright (C) 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.
- */
-
-#include "config.h"
-#include "playlist/despotify_playlist_plugin.h"
-#include "playlist_plugin.h"
-#include "playlist_list.h"
-#include "conf.h"
-#include "uri.h"
-#include "tag.h"
-#include "song.h"
-#include "input_stream.h"
-#include "despotify_utils.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-#include <stdlib.h>
-#include <despotify.h>
-
-struct despotify_playlist {
- struct playlist_provider base;
-
- struct despotify_session *session;
- GSList *list;
-};
-
-static void
-add_song(struct despotify_playlist *ctx, struct ds_track *track)
-{
- const char *dsp_scheme = despotify_playlist_plugin.schemes[0];
- struct song *song;
- char uri[128];
- char *ds_uri;
-
- /* Create a spt://... URI for MPD */
- g_snprintf(uri, sizeof(uri), "%s://", dsp_scheme);
- ds_uri = uri + strlen(dsp_scheme) + 3;
-
- if (despotify_track_to_uri(track, ds_uri) != ds_uri) {
- /* Should never really fail, but let's be sure */
- g_debug("Can't add track %s\n", track->title);
- return;
- }
-
- song = song_remote_new(uri);
- song->tag = mpd_despotify_tag_from_track(track);
-
- ctx->list = g_slist_prepend(ctx->list, song);
-}
-
-static bool
-parse_track(struct despotify_playlist *ctx,
- struct ds_link *link)
-{
- struct ds_track *track;
-
- track = despotify_link_get_track(ctx->session, link);
- if (!track)
- return false;
- add_song(ctx, track);
-
- return true;
-}
-
-static bool
-parse_playlist(struct despotify_playlist *ctx,
- struct ds_link *link)
-{
- struct ds_playlist *playlist;
- struct ds_track *track;
-
- playlist = despotify_link_get_playlist(ctx->session, link);
- if (!playlist)
- return false;
-
- for (track = playlist->tracks; track; track = track->next)
- add_song(ctx, track);
-
- return true;
-}
-
-static bool
-despotify_playlist_init(G_GNUC_UNUSED const struct config_param *param)
-{
- return true;
-}
-
-static void
-despotify_playlist_finish(void)
-{
-}
-
-
-static struct playlist_provider *
-despotify_playlist_open_uri(const char *url, G_GNUC_UNUSED GMutex *mutex,
- G_GNUC_UNUSED GCond *cond)
-{
- struct despotify_playlist *ctx;
- struct despotify_session *session;
- struct ds_link *link;
- bool parse_result;
-
- session = mpd_despotify_get_session();
- if (!session)
- goto clean_none;
-
- /* Get link without spt:// */
- link = despotify_link_from_uri(url + strlen(despotify_playlist_plugin.schemes[0]) + 3);
- if (!link) {
- g_debug("Can't find %s\n", url);
- goto clean_none;
- }
-
- ctx = g_new(struct despotify_playlist, 1);
-
- ctx->list = NULL;
- ctx->session = session;
- playlist_provider_init(&ctx->base, &despotify_playlist_plugin);
-
- switch (link->type)
- {
- case LINK_TYPE_TRACK:
- parse_result = parse_track(ctx, link);
- break;
- case LINK_TYPE_PLAYLIST:
- parse_result = parse_playlist(ctx, link);
- break;
- default:
- parse_result = false;
- break;
- }
- despotify_free_link(link);
- if (!parse_result)
- goto clean_playlist;
-
- ctx->list = g_slist_reverse(ctx->list);
-
- return &ctx->base;
-
-clean_playlist:
- g_slist_free(ctx->list);
-clean_none:
-
- return NULL;
-}
-
-static void
-track_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
-{
- struct song *song = (struct song *)data;
-
- song_free(song);
-}
-
-static void
-despotify_playlist_close(struct playlist_provider *_playlist)
-{
- struct despotify_playlist *ctx = (struct despotify_playlist *)_playlist;
-
- g_slist_foreach(ctx->list, track_free_callback, NULL);
- g_slist_free(ctx->list);
-
- g_free(ctx);
-}
-
-
-static struct song *
-despotify_playlist_read(struct playlist_provider *_playlist)
-{
- struct despotify_playlist *ctx = (struct despotify_playlist *)_playlist;
- struct song *out;
-
- if (!ctx->list)
- return NULL;
-
- /* Remove the current track */
- out = ctx->list->data;
- ctx->list = g_slist_remove(ctx->list, out);
-
- return out;
-}
-
-
-static const char *const despotify_schemes[] = {
- "spt",
- NULL
-};
-
-const struct playlist_plugin despotify_playlist_plugin = {
- .name = "despotify",
-
- .init = despotify_playlist_init,
- .finish = despotify_playlist_finish,
- .open_uri = despotify_playlist_open_uri,
- .read = despotify_playlist_read,
- .close = despotify_playlist_close,
-
- .schemes = despotify_schemes,
-};
diff --git a/src/playlist/despotify_playlist_plugin.h b/src/playlist/despotify_playlist_plugin.h
deleted file mode 100644
index f8ee20de0..000000000
--- a/src/playlist/despotify_playlist_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 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_PLAYLIST_DESPOTIFY_PLAYLIST_PLUGIN_H
-#define MPD_PLAYLIST_DESPOTIFY_PLAYLIST_PLUGIN_H
-
-extern const struct playlist_plugin despotify_playlist_plugin;
-
-#endif
diff --git a/src/playlist/embcue_playlist_plugin.c b/src/playlist/embcue_playlist_plugin.c
deleted file mode 100644
index 6d9a957f9..000000000
--- a/src/playlist/embcue_playlist_plugin.c
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * 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.
- */
-
-/** \file
- *
- * Playlist plugin that reads embedded cue sheets from the "CUESHEET"
- * tag of a music file.
- */
-
-#include "config.h"
-#include "playlist/embcue_playlist_plugin.h"
-#include "playlist_plugin.h"
-#include "tag.h"
-#include "tag_handler.h"
-#include "tag_file.h"
-#include "tag_ape.h"
-#include "tag_id3.h"
-#include "song.h"
-#include "cue/cue_parser.h"
-
-#include <glib.h>
-#include <assert.h>
-#include <string.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "cue"
-
-struct embcue_playlist {
- struct playlist_provider base;
-
- /**
- * This is an override for the CUE's "FILE". An embedded CUE
- * sheet must always point to the song file it is contained
- * in.
- */
- char *filename;
-
- /**
- * The value of the file's "CUESHEET" tag.
- */
- char *cuesheet;
-
- /**
- * The offset of the next line within "cuesheet".
- */
- char *next;
-
- struct cue_parser *parser;
-};
-
-static void
-embcue_tag_pair(const char *name, const char *value, void *ctx)
-{
- struct embcue_playlist *playlist = ctx;
-
- if (playlist->cuesheet == NULL &&
- g_ascii_strcasecmp(name, "cuesheet") == 0)
- playlist->cuesheet = g_strdup(value);
-}
-
-static const struct tag_handler embcue_tag_handler = {
- .pair = embcue_tag_pair,
-};
-
-static struct playlist_provider *
-embcue_playlist_open_uri(const char *uri,
- G_GNUC_UNUSED GMutex *mutex,
- G_GNUC_UNUSED GCond *cond)
-{
- if (!g_path_is_absolute(uri))
- /* only local files supported */
- return NULL;
-
- struct embcue_playlist *playlist = g_new(struct embcue_playlist, 1);
- playlist_provider_init(&playlist->base, &embcue_playlist_plugin);
- playlist->cuesheet = NULL;
-
- tag_file_scan(uri, &embcue_tag_handler, playlist);
- if (playlist->cuesheet == NULL) {
- tag_ape_scan2(uri, &embcue_tag_handler, playlist);
- if (playlist->cuesheet == NULL)
- tag_id3_scan(uri, &embcue_tag_handler, playlist);
- }
-
- if (playlist->cuesheet == NULL) {
- /* no "CUESHEET" tag found */
- g_free(playlist);
- return NULL;
- }
-
- playlist->filename = g_path_get_basename(uri);
-
- playlist->next = playlist->cuesheet;
- playlist->parser = cue_parser_new();
-
- return &playlist->base;
-}
-
-static void
-embcue_playlist_close(struct playlist_provider *_playlist)
-{
- struct embcue_playlist *playlist = (struct embcue_playlist *)_playlist;
-
- cue_parser_free(playlist->parser);
- g_free(playlist->cuesheet);
- g_free(playlist->filename);
- g_free(playlist);
-}
-
-static struct song *
-embcue_playlist_read(struct playlist_provider *_playlist)
-{
- struct embcue_playlist *playlist = (struct embcue_playlist *)_playlist;
-
- struct song *song = cue_parser_get(playlist->parser);
- if (song != NULL)
- return song;
-
- while (*playlist->next != 0) {
- const char *line = playlist->next;
- char *eol = strpbrk(playlist->next, "\r\n");
- if (eol != NULL) {
- /* null-terminate the line */
- *eol = 0;
- playlist->next = eol + 1;
- } else
- /* last line; put the "next" pointer to the
- end of the buffer */
- playlist->next += strlen(line);
-
- cue_parser_feed(playlist->parser, line);
- song = cue_parser_get(playlist->parser);
- if (song != NULL)
- return song_replace_uri(song, playlist->filename);
- }
-
- cue_parser_finish(playlist->parser);
- song = cue_parser_get(playlist->parser);
- if (song != NULL)
- song = song_replace_uri(song, playlist->filename);
- return song;
-}
-
-static const char *const embcue_playlist_suffixes[] = {
- /* a few codecs that are known to be supported; there are
- probably many more */
- "flac",
- "mp3", "mp2",
- "mp4", "mp4a", "m4b",
- "ape",
- "wv",
- "ogg", "oga",
- NULL
-};
-
-const struct playlist_plugin embcue_playlist_plugin = {
- .name = "cue",
-
- .open_uri = embcue_playlist_open_uri,
- .close = embcue_playlist_close,
- .read = embcue_playlist_read,
-
- .suffixes = embcue_playlist_suffixes,
- .mime_types = NULL,
-};
diff --git a/src/playlist/embcue_playlist_plugin.h b/src/playlist/embcue_playlist_plugin.h
deleted file mode 100644
index c5f21b27e..000000000
--- a/src/playlist/embcue_playlist_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef MPD_PLAYLIST_EMBCUE_PLAYLIST_PLUGIN_H
-#define MPD_PLAYLIST_EMBCUE_PLAYLIST_PLUGIN_H
-
-extern const struct playlist_plugin embcue_playlist_plugin;
-
-#endif
diff --git a/src/playlist/extm3u_playlist_plugin.c b/src/playlist/extm3u_playlist_plugin.c
deleted file mode 100644
index 19be8d1c4..000000000
--- a/src/playlist/extm3u_playlist_plugin.c
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "playlist/extm3u_playlist_plugin.h"
-#include "playlist_plugin.h"
-#include "text_input_stream.h"
-#include "uri.h"
-#include "song.h"
-#include "tag.h"
-#include "string_util.h"
-
-#include <glib.h>
-
-#include <string.h>
-#include <stdlib.h>
-
-struct extm3u_playlist {
- struct playlist_provider base;
-
- struct text_input_stream *tis;
-};
-
-static struct playlist_provider *
-extm3u_open_stream(struct input_stream *is)
-{
- struct extm3u_playlist *playlist;
- const char *line;
-
- playlist = g_new(struct extm3u_playlist, 1);
- playlist->tis = text_input_stream_new(is);
-
- line = text_input_stream_read(playlist->tis);
- if (line == NULL || strcmp(line, "#EXTM3U") != 0) {
- /* no EXTM3U header: fall back to the plain m3u
- plugin */
- text_input_stream_free(playlist->tis);
- g_free(playlist);
- return NULL;
- }
-
- playlist_provider_init(&playlist->base, &extm3u_playlist_plugin);
- return &playlist->base;
-}
-
-static void
-extm3u_close(struct playlist_provider *_playlist)
-{
- struct extm3u_playlist *playlist = (struct extm3u_playlist *)_playlist;
-
- text_input_stream_free(playlist->tis);
- g_free(playlist);
-}
-
-/**
- * Parse a EXTINF line.
- *
- * @param line the rest of the input line after the colon
- */
-static struct tag *
-extm3u_parse_tag(const char *line)
-{
- long duration;
- char *endptr;
- const char *name;
- struct tag *tag;
-
- duration = strtol(line, &endptr, 10);
- if (endptr[0] != ',')
- /* malformed line */
- return NULL;
-
- if (duration < 0)
- /* 0 means unknown duration */
- duration = 0;
-
- name = strchug_fast_c(endptr + 1);
- if (*name == 0 && duration == 0)
- /* no information available; don't allocate a tag
- object */
- return NULL;
-
- tag = tag_new();
- tag->time = 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_add_item(tag, TAG_NAME, name);
-
- return tag;
-}
-
-static struct song *
-extm3u_read(struct playlist_provider *_playlist)
-{
- struct extm3u_playlist *playlist = (struct extm3u_playlist *)_playlist;
- struct tag *tag = NULL;
- const char *line;
- struct song *song;
-
- do {
- line = text_input_stream_read(playlist->tis);
- if (line == NULL) {
- if (tag != NULL)
- tag_free(tag);
- return NULL;
- }
-
- if (g_str_has_prefix(line, "#EXTINF:")) {
- if (tag != NULL)
- tag_free(tag);
- tag = extm3u_parse_tag(line + 8);
- continue;
- }
-
- while (*line != 0 && g_ascii_isspace(*line))
- ++line;
- } while (line[0] == '#' || *line == 0);
-
- song = song_remote_new(line);
- song->tag = tag;
- return song;
-}
-
-static const char *const extm3u_suffixes[] = {
- "m3u",
- NULL
-};
-
-static const char *const extm3u_mime_types[] = {
- "audio/x-mpegurl",
- NULL
-};
-
-const struct playlist_plugin extm3u_playlist_plugin = {
- .name = "extm3u",
-
- .open_stream = extm3u_open_stream,
- .close = extm3u_close,
- .read = extm3u_read,
-
- .suffixes = extm3u_suffixes,
- .mime_types = extm3u_mime_types,
-};
diff --git a/src/playlist/extm3u_playlist_plugin.h b/src/playlist/extm3u_playlist_plugin.h
deleted file mode 100644
index 5f611ac9c..000000000
--- a/src/playlist/extm3u_playlist_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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_PLAYLIST_EXTM3U_PLAYLIST_PLUGIN_H
-#define MPD_PLAYLIST_EXTM3U_PLAYLIST_PLUGIN_H
-
-extern const struct playlist_plugin extm3u_playlist_plugin;
-
-#endif
diff --git a/src/playlist/lastfm_playlist_plugin.c b/src/playlist/lastfm_playlist_plugin.c
deleted file mode 100644
index ead14deaa..000000000
--- a/src/playlist/lastfm_playlist_plugin.c
+++ /dev/null
@@ -1,296 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "playlist/lastfm_playlist_plugin.h"
-#include "playlist_plugin.h"
-#include "playlist_list.h"
-#include "conf.h"
-#include "uri.h"
-#include "song.h"
-#include "input_stream.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-
-struct lastfm_playlist {
- struct playlist_provider base;
-
- struct input_stream *is;
-
- struct playlist_provider *xspf;
-};
-
-static struct {
- char *user;
- char *md5;
-} lastfm_config;
-
-static bool
-lastfm_init(const struct config_param *param)
-{
- const char *user = config_get_block_string(param, "user", NULL);
- const char *passwd = config_get_block_string(param, "password", NULL);
-
- if (user == NULL || passwd == NULL) {
- g_debug("disabling the last.fm playlist plugin "
- "because account is not configured");
- return false;
- }
-
- lastfm_config.user = g_uri_escape_string(user, NULL, false);
-
- if (strlen(passwd) != 32)
- lastfm_config.md5 = g_compute_checksum_for_string(G_CHECKSUM_MD5,
- passwd, strlen(passwd));
- else
- lastfm_config.md5 = g_strdup(passwd);
-
- return true;
-}
-
-static void
-lastfm_finish(void)
-{
- g_free(lastfm_config.user);
- g_free(lastfm_config.md5);
-}
-
-/**
- * Simple data fetcher.
- * @param url path or url of data to fetch.
- * @return data fetched, or NULL on error. Must be freed with g_free.
- */
-static char *
-lastfm_get(const char *url, GMutex *mutex, GCond *cond)
-{
- struct input_stream *input_stream;
- GError *error = NULL;
- char buffer[4096];
- size_t length = 0, nbytes;
-
- input_stream = input_stream_open(url, mutex, cond, &error);
- if (input_stream == NULL) {
- if (error != NULL) {
- g_warning("%s", error->message);
- g_error_free(error);
- }
-
- return NULL;
- }
-
- g_mutex_lock(mutex);
-
- input_stream_wait_ready(input_stream);
-
- do {
- nbytes = input_stream_read(input_stream, buffer + length,
- sizeof(buffer) - length, &error);
- if (nbytes == 0) {
- if (error != NULL) {
- g_warning("%s", error->message);
- g_error_free(error);
- }
-
- if (input_stream_eof(input_stream))
- break;
-
- /* I/O error */
- g_mutex_unlock(mutex);
- input_stream_close(input_stream);
- return NULL;
- }
-
- length += nbytes;
- } while (length < sizeof(buffer));
-
- g_mutex_unlock(mutex);
-
- input_stream_close(input_stream);
- return g_strndup(buffer, length);
-}
-
-/**
- * Ini-style value fetcher.
- * @param response data through which to search.
- * @param name name of value to search for.
- * @return value for param name in param response or NULL on error. Free with g_free.
- */
-static char *
-lastfm_find(const char *response, const char *name)
-{
- size_t name_length = strlen(name);
-
- while (true) {
- const char *eol = strchr(response, '\n');
- if (eol == NULL)
- return NULL;
-
- if (strncmp(response, name, name_length) == 0 &&
- response[name_length] == '=') {
- response += name_length + 1;
- return g_strndup(response, eol - response);
- }
-
- response = eol + 1;
- }
-}
-
-static struct playlist_provider *
-lastfm_open_uri(const char *uri, GMutex *mutex, GCond *cond)
-{
- struct lastfm_playlist *playlist;
- GError *error = NULL;
- char *p, *q, *response, *session;
-
- /* handshake */
-
- p = g_strconcat("http://ws.audioscrobbler.com/radio/handshake.php?"
- "version=1.1.1&platform=linux&"
- "username=", lastfm_config.user, "&"
- "passwordmd5=", lastfm_config.md5, "&"
- "debug=0&partner=", NULL);
- response = lastfm_get(p, mutex, cond);
- g_free(p);
- if (response == NULL)
- return NULL;
-
- /* extract session id from response */
-
- session = lastfm_find(response, "session");
- g_free(response);
- if (session == NULL) {
- g_warning("last.fm handshake failed");
- return NULL;
- }
-
- q = g_uri_escape_string(session, NULL, false);
- g_free(session);
- session = q;
-
- g_debug("session='%s'", session);
-
- /* "adjust" last.fm radio */
-
- if (strlen(uri) > 9) {
- char *escaped_uri;
-
- escaped_uri = g_uri_escape_string(uri, NULL, false);
-
- p = g_strconcat("http://ws.audioscrobbler.com/radio/adjust.php?"
- "session=", session, "&url=", escaped_uri, "&debug=0",
- NULL);
- g_free(escaped_uri);
-
- response = lastfm_get(p, mutex, cond);
- g_free(response);
- g_free(p);
-
- if (response == NULL) {
- g_free(session);
- return NULL;
- }
- }
-
- /* create the playlist object */
-
- playlist = g_new(struct lastfm_playlist, 1);
- playlist_provider_init(&playlist->base, &lastfm_playlist_plugin);
-
- /* open the last.fm playlist */
-
- p = g_strconcat("http://ws.audioscrobbler.com/radio/xspf.php?"
- "sk=", session, "&discovery=0&desktop=1.5.1.31879",
- NULL);
- g_free(session);
-
- playlist->is = input_stream_open(p, mutex, cond, &error);
- g_free(p);
-
- if (playlist->is == NULL) {
- if (error != NULL) {
- g_warning("Failed to load XSPF playlist: %s",
- error->message);
- g_error_free(error);
- } else
- g_warning("Failed to load XSPF playlist");
- g_free(playlist);
- return NULL;
- }
-
- g_mutex_lock(mutex);
-
- input_stream_wait_ready(playlist->is);
-
- /* last.fm does not send a MIME type, we have to fake it here
- :-( */
- g_free(playlist->is->mime);
- playlist->is->mime = g_strdup("application/xspf+xml");
-
- g_mutex_unlock(mutex);
-
- /* parse the XSPF playlist */
-
- playlist->xspf = playlist_list_open_stream(playlist->is, NULL);
- if (playlist->xspf == NULL) {
- input_stream_close(playlist->is);
- g_free(playlist);
- g_warning("Failed to parse XSPF playlist");
- return NULL;
- }
-
- return &playlist->base;
-}
-
-static void
-lastfm_close(struct playlist_provider *_playlist)
-{
- struct lastfm_playlist *playlist = (struct lastfm_playlist *)_playlist;
-
- playlist_plugin_close(playlist->xspf);
- input_stream_close(playlist->is);
- g_free(playlist);
-}
-
-static struct song *
-lastfm_read(struct playlist_provider *_playlist)
-{
- struct lastfm_playlist *playlist = (struct lastfm_playlist *)_playlist;
-
- return playlist_plugin_read(playlist->xspf);
-}
-
-static const char *const lastfm_schemes[] = {
- "lastfm",
- NULL
-};
-
-const struct playlist_plugin lastfm_playlist_plugin = {
- .name = "lastfm",
-
- .init = lastfm_init,
- .finish = lastfm_finish,
- .open_uri = lastfm_open_uri,
- .close = lastfm_close,
- .read = lastfm_read,
-
- .schemes = lastfm_schemes,
-};
diff --git a/src/playlist/lastfm_playlist_plugin.h b/src/playlist/lastfm_playlist_plugin.h
deleted file mode 100644
index 46a8b0caf..000000000
--- a/src/playlist/lastfm_playlist_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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_PLAYLIST_LASTFM_PLAYLIST_PLUGIN_H
-#define MPD_PLAYLIST_LASTFM_PLAYLIST_PLUGIN_H
-
-extern const struct playlist_plugin lastfm_playlist_plugin;
-
-#endif
diff --git a/src/playlist/m3u_playlist_plugin.c b/src/playlist/m3u_playlist_plugin.c
deleted file mode 100644
index 45b70d2b1..000000000
--- a/src/playlist/m3u_playlist_plugin.c
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "playlist/m3u_playlist_plugin.h"
-#include "playlist_plugin.h"
-#include "text_input_stream.h"
-#include "uri.h"
-#include "song.h"
-
-#include <glib.h>
-
-struct m3u_playlist {
- struct playlist_provider base;
-
- struct text_input_stream *tis;
-};
-
-static struct playlist_provider *
-m3u_open_stream(struct input_stream *is)
-{
- struct m3u_playlist *playlist = g_new(struct m3u_playlist, 1);
-
- playlist_provider_init(&playlist->base, &m3u_playlist_plugin);
- playlist->tis = text_input_stream_new(is);
-
- return &playlist->base;
-}
-
-static void
-m3u_close(struct playlist_provider *_playlist)
-{
- struct m3u_playlist *playlist = (struct m3u_playlist *)_playlist;
-
- text_input_stream_free(playlist->tis);
- g_free(playlist);
-}
-
-static struct song *
-m3u_read(struct playlist_provider *_playlist)
-{
- struct m3u_playlist *playlist = (struct m3u_playlist *)_playlist;
- const char *line;
-
- do {
- line = text_input_stream_read(playlist->tis);
- if (line == NULL)
- return NULL;
-
- while (*line != 0 && g_ascii_isspace(*line))
- ++line;
- } while (line[0] == '#' || *line == 0);
-
- return song_remote_new(line);
-}
-
-static const char *const m3u_suffixes[] = {
- "m3u",
- NULL
-};
-
-static const char *const m3u_mime_types[] = {
- "audio/x-mpegurl",
- NULL
-};
-
-const struct playlist_plugin m3u_playlist_plugin = {
- .name = "m3u",
-
- .open_stream = m3u_open_stream,
- .close = m3u_close,
- .read = m3u_read,
-
- .suffixes = m3u_suffixes,
- .mime_types = m3u_mime_types,
-};
diff --git a/src/playlist/m3u_playlist_plugin.h b/src/playlist/m3u_playlist_plugin.h
deleted file mode 100644
index 3890a5fc2..000000000
--- a/src/playlist/m3u_playlist_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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_PLAYLIST_M3U_PLAYLIST_PLUGIN_H
-#define MPD_PLAYLIST_M3U_PLAYLIST_PLUGIN_H
-
-extern const struct playlist_plugin m3u_playlist_plugin;
-
-#endif
diff --git a/src/playlist/pls_playlist_plugin.c b/src/playlist/pls_playlist_plugin.c
deleted file mode 100644
index c4e5492af..000000000
--- a/src/playlist/pls_playlist_plugin.c
+++ /dev/null
@@ -1,220 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "playlist/pls_playlist_plugin.h"
-#include "playlist_plugin.h"
-#include "input_stream.h"
-#include "uri.h"
-#include "song.h"
-#include "tag.h"
-#include <glib.h>
-
-struct pls_playlist {
- struct playlist_provider base;
-
- GSList *songs;
-};
-
-static void pls_parser(GKeyFile *keyfile, struct pls_playlist *playlist)
-{
- gchar *key;
- gchar *value;
- int length;
- GError *error = NULL;
- int num_entries = g_key_file_get_integer(keyfile, "playlist",
- "NumberOfEntries", &error);
- if (error) {
- g_debug("Invalid PLS file: '%s'", error->message);
- g_error_free(error);
- error = NULL;
-
- /* Hack to work around shoutcast failure to comform to spec */
- num_entries = g_key_file_get_integer(keyfile, "playlist",
- "numberofentries", &error);
- if (error) {
- g_error_free(error);
- error = NULL;
- }
- }
-
- while (num_entries > 0) {
- struct song *song;
- key = g_strdup_printf("File%i", num_entries);
- value = g_key_file_get_string(keyfile, "playlist", key,
- &error);
- if(error) {
- g_debug("Invalid PLS entry %s: '%s'",key, error->message);
- g_error_free(error);
- g_free(key);
- return;
- }
- g_free(key);
-
- song = song_remote_new(value);
- g_free(value);
-
- key = g_strdup_printf("Title%i", num_entries);
- value = g_key_file_get_string(keyfile, "playlist", key,
- &error);
- g_free(key);
- if(error == NULL && value){
- if (song->tag == NULL)
- song->tag = tag_new();
- tag_add_item(song->tag,TAG_TITLE, value);
- }
- /* Ignore errors? Most likely value not present */
- if(error) g_error_free(error);
- error = NULL;
- g_free(value);
-
- key = g_strdup_printf("Length%i", num_entries);
- length = g_key_file_get_integer(keyfile, "playlist", key,
- &error);
- g_free(key);
- if(error == NULL && length > 0){
- if (song->tag == NULL)
- song->tag = tag_new();
- song->tag->time = length;
- }
- /* Ignore errors? Most likely value not present */
- if(error) g_error_free(error);
- error = NULL;
-
- playlist->songs = g_slist_prepend(playlist->songs, song);
- num_entries--;
- }
-
-}
-
-static struct playlist_provider *
-pls_open_stream(struct input_stream *is)
-{
- GError *error = NULL;
- size_t nbytes;
- char buffer[1024];
- bool success;
- GKeyFile *keyfile;
- struct pls_playlist *playlist;
- GString *kf_data = g_string_new("");
-
- do {
- nbytes = input_stream_lock_read(is, buffer, sizeof(buffer),
- &error);
- if (nbytes == 0) {
- if (error != NULL) {
- g_string_free(kf_data, TRUE);
- g_warning("%s", error->message);
- g_error_free(error);
- return NULL;
- }
-
- break;
- }
-
- kf_data = g_string_append_len(kf_data, buffer,nbytes);
- /* Limit to 64k */
- } while(kf_data->len < 65536);
-
- if (kf_data->len == 0) {
- g_warning("KeyFile parser failed: No Data");
- g_string_free(kf_data, TRUE);
- return NULL;
- }
-
- keyfile = g_key_file_new();
- success = g_key_file_load_from_data(keyfile,
- kf_data->str, kf_data->len,
- G_KEY_FILE_NONE, &error);
-
- g_string_free(kf_data, TRUE);
-
- if (!success) {
- g_warning("KeyFile parser failed: %s", error->message);
- g_error_free(error);
- g_key_file_free(keyfile);
- return NULL;
- }
-
- playlist = g_new(struct pls_playlist, 1);
- playlist_provider_init(&playlist->base, &pls_playlist_plugin);
- playlist->songs = NULL;
-
- pls_parser(keyfile, playlist);
-
- g_key_file_free(keyfile);
- return &playlist->base;
-}
-
-
-static void
-song_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
-{
- struct song *song = data;
-
- song_free(song);
-}
-
-static void
-pls_close(struct playlist_provider *_playlist)
-{
- struct pls_playlist *playlist = (struct pls_playlist *)_playlist;
-
- g_slist_foreach(playlist->songs, song_free_callback, NULL);
- g_slist_free(playlist->songs);
-
- g_free(playlist);
-
-}
-
-static struct song *
-pls_read(struct playlist_provider *_playlist)
-{
- struct pls_playlist *playlist = (struct pls_playlist *)_playlist;
- struct song *song;
-
- if (playlist->songs == NULL)
- return NULL;
-
- song = playlist->songs->data;
- playlist->songs = g_slist_remove(playlist->songs, song);
-
- return song;
-}
-
-static const char *const pls_suffixes[] = {
- "pls",
- NULL
-};
-
-static const char *const pls_mime_types[] = {
- "audio/x-scpls",
- NULL
-};
-
-const struct playlist_plugin pls_playlist_plugin = {
- .name = "pls",
-
- .open_stream = pls_open_stream,
- .close = pls_close,
- .read = pls_read,
-
- .suffixes = pls_suffixes,
- .mime_types = pls_mime_types,
-};
diff --git a/src/playlist/pls_playlist_plugin.h b/src/playlist/pls_playlist_plugin.h
deleted file mode 100644
index d03435f6d..000000000
--- a/src/playlist/pls_playlist_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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_PLAYLIST_PLS_PLAYLIST_PLUGIN_H
-#define MPD_PLAYLIST_PLS_PLAYLIST_PLUGIN_H
-
-extern const struct playlist_plugin pls_playlist_plugin;
-
-#endif
diff --git a/src/playlist/rss_playlist_plugin.c b/src/playlist/rss_playlist_plugin.c
deleted file mode 100644
index 6740cba7e..000000000
--- a/src/playlist/rss_playlist_plugin.c
+++ /dev/null
@@ -1,322 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "playlist/rss_playlist_plugin.h"
-#include "playlist_plugin.h"
-#include "input_stream.h"
-#include "song.h"
-#include "tag.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "rss"
-
-/**
- * This is the state object for the GLib XML parser.
- */
-struct rss_parser {
- /**
- * The list of songs (in reverse order because that's faster
- * while adding).
- */
- GSList *songs;
-
- /**
- * The current position in the XML file.
- */
- enum {
- ROOT, ITEM,
- } state;
-
- /**
- * The current tag within the "entry" element. This is only
- * valid if state==ITEM. TAG_NUM_OF_ITEM_TYPES means there
- * is no (known) tag.
- */
- enum tag_type tag;
-
- /**
- * The current song. It is allocated after the "location"
- * element.
- */
- struct song *song;
-};
-
-static const gchar *
-get_attribute(const gchar **attribute_names, const gchar **attribute_values,
- const gchar *name)
-{
- for (unsigned i = 0; attribute_names[i] != NULL; ++i)
- if (g_ascii_strcasecmp(attribute_names[i], name) == 0)
- return attribute_values[i];
-
- return NULL;
-}
-
-static void
-rss_start_element(G_GNUC_UNUSED GMarkupParseContext *context,
- const gchar *element_name,
- const gchar **attribute_names,
- const gchar **attribute_values,
- gpointer user_data, G_GNUC_UNUSED GError **error)
-{
- struct rss_parser *parser = user_data;
-
- switch (parser->state) {
- case ROOT:
- if (g_ascii_strcasecmp(element_name, "item") == 0) {
- parser->state = ITEM;
- parser->song = song_remote_new("rss:");
- parser->tag = TAG_NUM_OF_ITEM_TYPES;
- }
-
- break;
-
- case ITEM:
- if (g_ascii_strcasecmp(element_name, "enclosure") == 0) {
- const gchar *href = get_attribute(attribute_names,
- attribute_values,
- "url");
- if (href != NULL) {
- /* create new song object, and copy
- the existing tag over; we cannot
- replace the existing song's URI,
- because that attribute is
- immutable */
- struct song *song = song_remote_new(href);
-
- if (parser->song != NULL) {
- song->tag = parser->song->tag;
- parser->song->tag = NULL;
- song_free(parser->song);
- }
-
- parser->song = song;
- }
- } else if (g_ascii_strcasecmp(element_name, "title") == 0)
- parser->tag = TAG_TITLE;
- else if (g_ascii_strcasecmp(element_name, "itunes:author") == 0)
- parser->tag = TAG_ARTIST;
-
- break;
- }
-}
-
-static void
-rss_end_element(G_GNUC_UNUSED GMarkupParseContext *context,
- const gchar *element_name,
- gpointer user_data, G_GNUC_UNUSED GError **error)
-{
- struct rss_parser *parser = user_data;
-
- switch (parser->state) {
- case ROOT:
- break;
-
- case ITEM:
- if (g_ascii_strcasecmp(element_name, "item") == 0) {
- if (strcmp(parser->song->uri, "rss:") != 0)
- parser->songs = g_slist_prepend(parser->songs,
- parser->song);
- else
- song_free(parser->song);
-
- parser->state = ROOT;
- } else
- parser->tag = TAG_NUM_OF_ITEM_TYPES;
-
- break;
- }
-}
-
-static void
-rss_text(G_GNUC_UNUSED GMarkupParseContext *context,
- const gchar *text, gsize text_len,
- gpointer user_data, G_GNUC_UNUSED GError **error)
-{
- struct rss_parser *parser = user_data;
-
- switch (parser->state) {
- case ROOT:
- break;
-
- case ITEM:
- if (parser->tag != TAG_NUM_OF_ITEM_TYPES) {
- if (parser->song->tag == NULL)
- parser->song->tag = tag_new();
- tag_add_item_n(parser->song->tag, parser->tag,
- text, text_len);
- }
-
- break;
- }
-}
-
-static const GMarkupParser rss_parser = {
- .start_element = rss_start_element,
- .end_element = rss_end_element,
- .text = rss_text,
-};
-
-static void
-song_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
-{
- struct song *song = data;
-
- song_free(song);
-}
-
-static void
-rss_parser_destroy(gpointer data)
-{
- struct rss_parser *parser = data;
-
- if (parser->state >= ITEM)
- song_free(parser->song);
-
- g_slist_foreach(parser->songs, song_free_callback, NULL);
- g_slist_free(parser->songs);
-}
-
-/*
- * The playlist object
- *
- */
-
-struct rss_playlist {
- struct playlist_provider base;
-
- GSList *songs;
-};
-
-static struct playlist_provider *
-rss_open_stream(struct input_stream *is)
-{
- struct rss_parser parser = {
- .songs = NULL,
- .state = ROOT,
- };
- struct rss_playlist *playlist;
- GMarkupParseContext *context;
- char buffer[1024];
- size_t nbytes;
- bool success;
- GError *error = NULL;
-
- /* 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 = input_stream_lock_read(is, buffer, sizeof(buffer),
- &error);
- if (nbytes == 0) {
- if (error != NULL) {
- g_markup_parse_context_free(context);
- g_warning("%s", error->message);
- g_error_free(error);
- return NULL;
- }
-
- break;
- }
-
- success = g_markup_parse_context_parse(context, buffer, nbytes,
- &error);
- if (!success) {
- g_warning("XML parser failed: %s", error->message);
- g_error_free(error);
- g_markup_parse_context_free(context);
- return NULL;
- }
- }
-
- success = g_markup_parse_context_end_parse(context, &error);
- if (!success) {
- g_warning("XML parser failed: %s", error->message);
- g_error_free(error);
- g_markup_parse_context_free(context);
- return NULL;
- }
-
- /* create a #rss_playlist object from the parsed song list */
-
- playlist = g_new(struct rss_playlist, 1);
- playlist_provider_init(&playlist->base, &rss_playlist_plugin);
- playlist->songs = g_slist_reverse(parser.songs);
- parser.songs = NULL;
-
- g_markup_parse_context_free(context);
-
- return &playlist->base;
-}
-
-static void
-rss_close(struct playlist_provider *_playlist)
-{
- struct rss_playlist *playlist = (struct rss_playlist *)_playlist;
-
- g_slist_foreach(playlist->songs, song_free_callback, NULL);
- g_slist_free(playlist->songs);
- g_free(playlist);
-}
-
-static struct song *
-rss_read(struct playlist_provider *_playlist)
-{
- struct rss_playlist *playlist = (struct rss_playlist *)_playlist;
- struct song *song;
-
- if (playlist->songs == NULL)
- return NULL;
-
- song = playlist->songs->data;
- playlist->songs = g_slist_remove(playlist->songs, song);
-
- return song;
-}
-
-static const char *const rss_suffixes[] = {
- "rss",
- NULL
-};
-
-static const char *const rss_mime_types[] = {
- "application/rss+xml",
- "text/xml",
- NULL
-};
-
-const struct playlist_plugin rss_playlist_plugin = {
- .name = "rss",
-
- .open_stream = rss_open_stream,
- .close = rss_close,
- .read = rss_read,
-
- .suffixes = rss_suffixes,
- .mime_types = rss_mime_types,
-};
diff --git a/src/playlist/rss_playlist_plugin.h b/src/playlist/rss_playlist_plugin.h
deleted file mode 100644
index 3b376de79..000000000
--- a/src/playlist/rss_playlist_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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_PLAYLIST_RSS_PLAYLIST_PLUGIN_H
-#define MPD_PLAYLIST_RSS_PLAYLIST_PLUGIN_H
-
-extern const struct playlist_plugin rss_playlist_plugin;
-
-#endif
diff --git a/src/playlist/soundcloud_playlist_plugin.c b/src/playlist/soundcloud_playlist_plugin.c
deleted file mode 100644
index 7c79f880a..000000000
--- a/src/playlist/soundcloud_playlist_plugin.c
+++ /dev/null
@@ -1,448 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "playlist/soundcloud_playlist_plugin.h"
-#include "conf.h"
-#include "input_stream.h"
-#include "playlist_plugin.h"
-#include "song.h"
-#include "tag.h"
-
-#include <glib.h>
-#include <yajl/yajl_parse.h>
-
-#include <string.h>
-
-struct soundcloud_playlist {
- struct playlist_provider base;
-
- GSList *songs;
-};
-
-static struct {
- char *apikey;
-} soundcloud_config;
-
-static bool
-soundcloud_init(const struct config_param *param)
-{
- soundcloud_config.apikey =
- config_dup_block_string(param, "apikey", NULL);
- if (soundcloud_config.apikey == NULL) {
- g_debug("disabling the soundcloud playlist plugin "
- "because API key is not set");
- return false;
- }
-
- return true;
-}
-
-static void
-soundcloud_finish(void)
-{
- g_free(soundcloud_config.apikey);
-}
-
-/**
- * Construct a full soundcloud resolver URL from the given fragment.
- * @param uri uri of a soundcloud page (or just the path)
- * @return Constructed URL. Must be freed with g_free.
- */
-static char *
-soundcloud_resolve(const char* uri) {
- char *u, *ru;
-
- if (g_str_has_prefix(uri, "http://")) {
- u = g_strdup(uri);
- } else if (g_str_has_prefix(uri, "soundcloud.com")) {
- u = g_strconcat("http://", uri, NULL);
- } else {
- /* assume it's just a path on soundcloud.com */
- u = g_strconcat("http://soundcloud.com/", uri, NULL);
- }
-
- ru = g_strconcat("http://api.soundcloud.com/resolve.json?url=",
- u, "&client_id=", soundcloud_config.apikey, NULL);
- g_free(u);
-
- return ru;
-}
-
-/* YAJL parser for track data from both /tracks/ and /playlists/ JSON */
-
-enum key {
- Duration,
- Title,
- Stream_URL,
- Other,
-};
-
-const char* key_str[] = {
- "duration",
- "title",
- "stream_url",
- NULL,
-};
-
-struct parse_data {
- int key;
- char* stream_url;
- long duration;
- char* title;
- int got_url; /* nesting level of last stream_url */
- GSList* songs;
-};
-
-static int handle_integer(void *ctx,
- long
-#ifndef HAVE_YAJL1
- long
-#endif
- intval)
-{
- struct parse_data *data = (struct parse_data *) ctx;
-
- switch (data->key) {
- case Duration:
- data->duration = intval;
- break;
- default:
- break;
- }
-
- return 1;
-}
-
-static int handle_string(void *ctx, const unsigned char* stringval,
-#ifdef HAVE_YAJL1
- unsigned int
-#else
- size_t
-#endif
- stringlen)
-{
- struct parse_data *data = (struct parse_data *) ctx;
- const char *s = (const char *) stringval;
-
- switch (data->key) {
- case Title:
- if (data->title != NULL)
- g_free(data->title);
- data->title = g_strndup(s, stringlen);
- break;
- case Stream_URL:
- if (data->stream_url != NULL)
- g_free(data->stream_url);
- data->stream_url = g_strndup(s, stringlen);
- data->got_url = 1;
- break;
- default:
- break;
- }
-
- return 1;
-}
-
-static int handle_mapkey(void *ctx, const unsigned char* stringval,
-#ifdef HAVE_YAJL1
- unsigned int
-#else
- size_t
-#endif
- stringlen)
-{
- struct parse_data *data = (struct parse_data *) ctx;
-
- int i;
- data->key = Other;
-
- for (i = 0; i < Other; ++i) {
- if (strncmp((const char *)stringval, key_str[i], stringlen) == 0) {
- data->key = i;
- break;
- }
- }
-
- return 1;
-}
-
-static int handle_start_map(void *ctx)
-{
- struct parse_data *data = (struct parse_data *) ctx;
-
- if (data->got_url > 0)
- data->got_url++;
-
- return 1;
-}
-
-static int handle_end_map(void *ctx)
-{
- struct parse_data *data = (struct parse_data *) ctx;
-
- if (data->got_url > 1) {
- data->got_url--;
- return 1;
- }
-
- if (data->got_url == 0)
- return 1;
-
- /* got_url == 1, track finished, make it into a song */
- data->got_url = 0;
-
- struct song *s;
- struct tag *t;
- char *u;
-
- u = g_strconcat(data->stream_url, "?client_id=", soundcloud_config.apikey, NULL);
- s = song_remote_new(u);
- g_free(u);
- t = tag_new();
- t->time = data->duration / 1000;
- if (data->title != NULL)
- tag_add_item(t, TAG_NAME, data->title);
- s->tag = t;
-
- data->songs = g_slist_prepend(data->songs, s);
-
- return 1;
-}
-
-static yajl_callbacks parse_callbacks = {
- NULL,
- NULL,
- handle_integer,
- NULL,
- NULL,
- handle_string,
- handle_start_map,
- handle_mapkey,
- handle_end_map,
- NULL,
- NULL,
-};
-
-/**
- * Read JSON data and parse it using the given YAJL parser.
- * @param url URL of the JSON data.
- * @param hand YAJL parser handle.
- * @return -1 on error, 0 on success.
- */
-static int
-soundcloud_parse_json(const char *url, yajl_handle hand, GMutex* mutex, GCond* cond)
-{
- struct input_stream *input_stream;
- GError *error = NULL;
- char buffer[4096];
- unsigned char *ubuffer = (unsigned char *)buffer;
- size_t nbytes;
-
- input_stream = input_stream_open(url, mutex, cond, &error);
- if (input_stream == NULL) {
- if (error != NULL) {
- g_warning("%s", error->message);
- g_error_free(error);
- }
- return -1;
- }
-
- g_mutex_lock(mutex);
- input_stream_wait_ready(input_stream);
-
- yajl_status stat;
- int done = 0;
-
- while (!done) {
- nbytes = input_stream_read(input_stream, buffer, sizeof(buffer), &error);
- if (nbytes == 0) {
- if (error != NULL) {
- g_warning("%s", error->message);
- g_error_free(error);
- }
- if (input_stream_eof(input_stream)) {
- done = true;
- } else {
- g_mutex_unlock(mutex);
- input_stream_close(input_stream);
- return -1;
- }
- }
-
- if (done) {
-#ifdef HAVE_YAJL1
- stat = yajl_parse_complete(hand);
-#else
- stat = yajl_complete_parse(hand);
-#endif
- } else
- stat = yajl_parse(hand, ubuffer, nbytes);
-
- if (stat != yajl_status_ok
-#ifdef HAVE_YAJL1
- && stat != yajl_status_insufficient_data
-#endif
- )
- {
- unsigned char *str = yajl_get_error(hand, 1, ubuffer, nbytes);
- g_warning("%s", str);
- yajl_free_error(hand, str);
- break;
- }
- }
-
- g_mutex_unlock(mutex);
- input_stream_close(input_stream);
-
- return 0;
-}
-
-/**
- * Parse a soundcloud:// URL and create a playlist.
- * @param uri A soundcloud URL. Accepted forms:
- * soundcloud://track/<track-id>
- * soundcloud://playlist/<playlist-id>
- * soundcloud://url/<url or path of soundcloud page>
- */
-
-static struct playlist_provider *
-soundcloud_open_uri(const char *uri, GMutex *mutex, GCond *cond)
-{
- struct soundcloud_playlist *playlist = NULL;
-
- 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) {
- g_warning("incompatible scheme for soundcloud plugin: %s", scheme);
- g_free(s);
- return NULL;
- }
-
- char *u = NULL;
- if (strcmp(arg, "track") == 0) {
- u = g_strconcat("http://api.soundcloud.com/tracks/",
- rest, ".json?client_id=", soundcloud_config.apikey, NULL);
- } else if (strcmp(arg, "playlist") == 0) {
- u = g_strconcat("http://api.soundcloud.com/playlists/",
- rest, ".json?client_id=", soundcloud_config.apikey, NULL);
- } else if (strcmp(arg, "url") == 0) {
- /* Translate to soundcloud resolver call. libcurl will automatically
- follow the redirect to the right resource. */
- u = soundcloud_resolve(rest);
- }
- g_free(s);
-
- if (u == NULL) {
- g_warning("unknown soundcloud URI");
- return NULL;
- }
-
- yajl_handle hand;
- struct parse_data data;
-
- data.got_url = 0;
- data.songs = NULL;
- data.title = NULL;
- data.stream_url = NULL;
-#ifdef HAVE_YAJL1
- hand = yajl_alloc(&parse_callbacks, NULL, NULL, (void *) &data);
-#else
- hand = yajl_alloc(&parse_callbacks, NULL, (void *) &data);
-#endif
-
- int ret = soundcloud_parse_json(u, hand, mutex, cond);
-
- g_free(u);
- yajl_free(hand);
- if (data.title != NULL)
- g_free(data.title);
- if (data.stream_url != NULL)
- g_free(data.stream_url);
-
- if (ret == -1)
- return NULL;
-
- playlist = g_new(struct soundcloud_playlist, 1);
- playlist_provider_init(&playlist->base, &soundcloud_playlist_plugin);
- playlist->songs = g_slist_reverse(data.songs);
-
- return &playlist->base;
-}
-
-static void
-soundcloud_close(struct playlist_provider *_playlist)
-{
- struct soundcloud_playlist *playlist = (struct soundcloud_playlist *)_playlist;
-
- g_free(playlist);
-}
-
-
-static struct song *
-soundcloud_read(struct playlist_provider *_playlist)
-{
- struct soundcloud_playlist *playlist = (struct soundcloud_playlist *)_playlist;
-
- if (playlist->songs == NULL)
- return NULL;
-
- struct song* s;
- s = (struct song *)playlist->songs->data;
- playlist->songs = g_slist_remove(playlist->songs, s);
- return s;
-}
-
-static const char *const soundcloud_schemes[] = {
- "soundcloud",
- NULL
-};
-
-const struct playlist_plugin soundcloud_playlist_plugin = {
- .name = "soundcloud",
-
- .init = soundcloud_init,
- .finish = soundcloud_finish,
- .open_uri = soundcloud_open_uri,
- .close = soundcloud_close,
- .read = soundcloud_read,
-
- .schemes = soundcloud_schemes,
-};
-
-
diff --git a/src/playlist/soundcloud_playlist_plugin.h b/src/playlist/soundcloud_playlist_plugin.h
deleted file mode 100644
index e09e2dd46..000000000
--- a/src/playlist/soundcloud_playlist_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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_PLAYLIST_SOUNDCLOUD_PLAYLIST_PLUGIN_H
-#define MPD_PLAYLIST_SOUNDCLOUD_PLAYLIST_PLUGIN_H
-
-extern const struct playlist_plugin soundcloud_playlist_plugin;
-
-#endif
diff --git a/src/playlist/xspf_playlist_plugin.c b/src/playlist/xspf_playlist_plugin.c
deleted file mode 100644
index 17d9040e2..000000000
--- a/src/playlist/xspf_playlist_plugin.c
+++ /dev/null
@@ -1,343 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "playlist/xspf_playlist_plugin.h"
-#include "playlist_plugin.h"
-#include "input_stream.h"
-#include "uri.h"
-#include "song.h"
-#include "tag.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "xspf"
-
-/**
- * This is the state object for the GLib XML parser.
- */
-struct xspf_parser {
- /**
- * The list of songs (in reverse order because that's faster
- * while adding).
- */
- GSList *songs;
-
- /**
- * The current position in the XML file.
- */
- enum {
- ROOT, PLAYLIST, TRACKLIST, TRACK,
- LOCATION,
- } state;
-
- /**
- * The current tag within the "track" element. This is only
- * valid if state==TRACK. TAG_NUM_OF_ITEM_TYPES means there
- * is no (known) tag.
- */
- enum tag_type tag;
-
- /**
- * The current song. It is allocated after the "location"
- * element.
- */
- struct song *song;
-};
-
-static void
-xspf_start_element(G_GNUC_UNUSED GMarkupParseContext *context,
- const gchar *element_name,
- G_GNUC_UNUSED const gchar **attribute_names,
- G_GNUC_UNUSED const gchar **attribute_values,
- gpointer user_data, G_GNUC_UNUSED GError **error)
-{
- struct xspf_parser *parser = user_data;
-
- switch (parser->state) {
- case ROOT:
- if (strcmp(element_name, "playlist") == 0)
- parser->state = PLAYLIST;
-
- break;
-
- case PLAYLIST:
- if (strcmp(element_name, "trackList") == 0)
- parser->state = TRACKLIST;
-
- break;
-
- case TRACKLIST:
- if (strcmp(element_name, "track") == 0) {
- parser->state = TRACK;
- parser->song = NULL;
- parser->tag = TAG_NUM_OF_ITEM_TYPES;
- }
-
- break;
-
- case TRACK:
- if (strcmp(element_name, "location") == 0)
- parser->state = LOCATION;
- else if (strcmp(element_name, "title") == 0)
- parser->tag = TAG_TITLE;
- else if (strcmp(element_name, "creator") == 0)
- /* TAG_COMPOSER would be more correct
- according to the XSPF spec */
- parser->tag = TAG_ARTIST;
- else if (strcmp(element_name, "annotation") == 0)
- parser->tag = TAG_COMMENT;
- else if (strcmp(element_name, "album") == 0)
- parser->tag = TAG_ALBUM;
- else if (strcmp(element_name, "trackNum") == 0)
- parser->tag = TAG_TRACK;
-
- break;
-
- case LOCATION:
- break;
- }
-}
-
-static void
-xspf_end_element(G_GNUC_UNUSED GMarkupParseContext *context,
- const gchar *element_name,
- gpointer user_data, G_GNUC_UNUSED GError **error)
-{
- struct xspf_parser *parser = user_data;
-
- switch (parser->state) {
- case ROOT:
- break;
-
- case PLAYLIST:
- if (strcmp(element_name, "playlist") == 0)
- parser->state = ROOT;
-
- break;
-
- case TRACKLIST:
- if (strcmp(element_name, "tracklist") == 0)
- parser->state = PLAYLIST;
-
- break;
-
- case TRACK:
- if (strcmp(element_name, "track") == 0) {
- if (parser->song != NULL)
- parser->songs = g_slist_prepend(parser->songs,
- parser->song);
-
- parser->state = TRACKLIST;
- } else
- parser->tag = TAG_NUM_OF_ITEM_TYPES;
-
- break;
-
- case LOCATION:
- parser->state = TRACK;
- break;
- }
-}
-
-static void
-xspf_text(G_GNUC_UNUSED GMarkupParseContext *context,
- const gchar *text, gsize text_len,
- gpointer user_data, G_GNUC_UNUSED GError **error)
-{
- struct xspf_parser *parser = user_data;
-
- switch (parser->state) {
- case ROOT:
- case PLAYLIST:
- case TRACKLIST:
- break;
-
- case TRACK:
- if (parser->song != NULL &&
- parser->tag != TAG_NUM_OF_ITEM_TYPES) {
- if (parser->song->tag == NULL)
- parser->song->tag = tag_new();
- tag_add_item_n(parser->song->tag, parser->tag,
- text, text_len);
- }
-
- break;
-
- case LOCATION:
- if (parser->song == NULL) {
- char *uri = g_strndup(text, text_len);
- parser->song = song_remote_new(uri);
- g_free(uri);
- }
-
- break;
- }
-}
-
-static const GMarkupParser xspf_parser = {
- .start_element = xspf_start_element,
- .end_element = xspf_end_element,
- .text = xspf_text,
-};
-
-static void
-song_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
-{
- struct song *song = data;
-
- song_free(song);
-}
-
-static void
-xspf_parser_destroy(gpointer data)
-{
- struct xspf_parser *parser = data;
-
- if (parser->state >= TRACK && parser->song != NULL)
- song_free(parser->song);
-
- g_slist_foreach(parser->songs, song_free_callback, NULL);
- g_slist_free(parser->songs);
-}
-
-/*
- * The playlist object
- *
- */
-
-struct xspf_playlist {
- struct playlist_provider base;
-
- GSList *songs;
-};
-
-static struct playlist_provider *
-xspf_open_stream(struct input_stream *is)
-{
- struct xspf_parser parser = {
- .songs = NULL,
- .state = ROOT,
- };
- struct xspf_playlist *playlist;
- GMarkupParseContext *context;
- char buffer[1024];
- size_t nbytes;
- bool success;
- GError *error = NULL;
-
- /* 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 = input_stream_lock_read(is, buffer, sizeof(buffer),
- &error);
- if (nbytes == 0) {
- if (error != NULL) {
- g_markup_parse_context_free(context);
- g_warning("%s", error->message);
- g_error_free(error);
- return NULL;
- }
-
- break;
- }
-
- success = g_markup_parse_context_parse(context, buffer, nbytes,
- &error);
- if (!success) {
- g_warning("XML parser failed: %s", error->message);
- g_error_free(error);
- g_markup_parse_context_free(context);
- return NULL;
- }
- }
-
- success = g_markup_parse_context_end_parse(context, &error);
- if (!success) {
- g_warning("XML parser failed: %s", error->message);
- g_error_free(error);
- g_markup_parse_context_free(context);
- return NULL;
- }
-
- /* create a #xspf_playlist object from the parsed song list */
-
- playlist = g_new(struct xspf_playlist, 1);
- playlist_provider_init(&playlist->base, &xspf_playlist_plugin);
- playlist->songs = g_slist_reverse(parser.songs);
- parser.songs = NULL;
-
- g_markup_parse_context_free(context);
-
- return &playlist->base;
-}
-
-static void
-xspf_close(struct playlist_provider *_playlist)
-{
- struct xspf_playlist *playlist = (struct xspf_playlist *)_playlist;
-
- g_slist_foreach(playlist->songs, song_free_callback, NULL);
- g_slist_free(playlist->songs);
- g_free(playlist);
-}
-
-static struct song *
-xspf_read(struct playlist_provider *_playlist)
-{
- struct xspf_playlist *playlist = (struct xspf_playlist *)_playlist;
- struct song *song;
-
- if (playlist->songs == NULL)
- return NULL;
-
- song = playlist->songs->data;
- playlist->songs = g_slist_remove(playlist->songs, song);
-
- return song;
-}
-
-static const char *const xspf_suffixes[] = {
- "xspf",
- NULL
-};
-
-static const char *const xspf_mime_types[] = {
- "application/xspf+xml",
- NULL
-};
-
-const struct playlist_plugin xspf_playlist_plugin = {
- .name = "xspf",
-
- .open_stream = xspf_open_stream,
- .close = xspf_close,
- .read = xspf_read,
-
- .suffixes = xspf_suffixes,
- .mime_types = xspf_mime_types,
-};
diff --git a/src/playlist/xspf_playlist_plugin.h b/src/playlist/xspf_playlist_plugin.h
deleted file mode 100644
index 4636d7e83..000000000
--- a/src/playlist/xspf_playlist_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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_PLAYLIST_XSPF_PLAYLIST_PLUGIN_H
-#define MPD_PLAYLIST_XSPF_PLAYLIST_PLUGIN_H
-
-extern const struct playlist_plugin xspf_playlist_plugin;
-
-#endif
diff --git a/src/playlist_any.c b/src/playlist_any.c
deleted file mode 100644
index 450ca5932..000000000
--- a/src/playlist_any.c
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "playlist_any.h"
-#include "playlist_list.h"
-#include "playlist_mapper.h"
-#include "uri.h"
-#include "input_stream.h"
-
-#include <assert.h>
-
-static struct playlist_provider *
-playlist_open_remote(const char *uri, GMutex *mutex, GCond *cond,
- struct input_stream **is_r)
-{
- assert(uri_has_scheme(uri));
-
- struct playlist_provider *playlist =
- playlist_list_open_uri(uri, mutex, cond);
- if (playlist != NULL) {
- *is_r = NULL;
- return playlist;
- }
-
- GError *error = NULL;
- struct input_stream *is = input_stream_open(uri, mutex, cond, &error);
- if (is == NULL) {
- if (error != NULL) {
- g_warning("Failed to open %s: %s",
- uri, error->message);
- g_error_free(error);
- }
-
- return NULL;
- }
-
- playlist = playlist_list_open_stream(is, uri);
- if (playlist == NULL) {
- input_stream_close(is);
- return NULL;
- }
-
- *is_r = is;
- return playlist;
-}
-
-struct playlist_provider *
-playlist_open_any(const char *uri, GMutex *mutex, GCond *cond,
- struct input_stream **is_r)
-{
- return uri_has_scheme(uri)
- ? playlist_open_remote(uri, mutex, cond, is_r)
- : playlist_mapper_open(uri, mutex, cond, is_r);
-}
diff --git a/src/playlist_any.h b/src/playlist_any.h
deleted file mode 100644
index 310913de9..000000000
--- a/src/playlist_any.h
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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_PLAYLIST_ANY_H
-#define MPD_PLAYLIST_ANY_H
-
-#include <glib.h>
-
-struct playlist_provider;
-struct input_stream;
-
-/**
- * Opens a playlist from the specified URI, which can be either an
- * absolute remote URI (with a scheme) or a relative path to the
- * music orplaylist directory.
- *
- * @param is_r on success, an input_stream object may be returned
- * here, which must be closed after the playlist_provider object is
- * freed
- */
-struct playlist_provider *
-playlist_open_any(const char *uri, GMutex *mutex, GCond *cond,
- struct input_stream **is_r);
-
-#endif
diff --git a/src/playlist_control.c b/src/playlist_control.c
deleted file mode 100644
index 0dea7676a..000000000
--- a/src/playlist_control.c
+++ /dev/null
@@ -1,288 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * Functions for controlling playback on the playlist level.
- *
- */
-
-#include "config.h"
-#include "playlist_internal.h"
-#include "player_control.h"
-#include "idle.h"
-
-#include <glib.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "playlist"
-
-void
-playlist_stop(struct playlist *playlist, struct player_control *pc)
-{
- if (!playlist->playing)
- return;
-
- assert(playlist->current >= 0);
-
- g_debug("stop");
- pc_stop(pc);
- playlist->queued = -1;
- playlist->playing = false;
-
- if (playlist->queue.random) {
- /* shuffle the playlist, so the next playback will
- result in a new random order */
-
- unsigned current_position =
- queue_order_to_position(&playlist->queue,
- playlist->current);
-
- queue_shuffle_order(&playlist->queue);
-
- /* make sure that "current" stays valid, and the next
- "play" command plays the same song again */
- playlist->current =
- queue_position_to_order(&playlist->queue,
- current_position);
- }
-}
-
-enum playlist_result
-playlist_play(struct playlist *playlist, struct player_control *pc,
- int song)
-{
- unsigned i = song;
-
- pc_clear_error(pc);
-
- if (song == -1) {
- /* play any song ("current" song, or the first song */
-
- if (queue_is_empty(&playlist->queue))
- return PLAYLIST_RESULT_SUCCESS;
-
- if (playlist->playing) {
- /* already playing: unpause playback, just in
- case it was paused, and return */
- pc_set_pause(pc, false);
- return PLAYLIST_RESULT_SUCCESS;
- }
-
- /* select a song: "current" song, or the first one */
- i = playlist->current >= 0
- ? playlist->current
- : 0;
- } else if (!queue_valid_position(&playlist->queue, song))
- return PLAYLIST_RESULT_BAD_RANGE;
-
- if (playlist->queue.random) {
- if (song >= 0)
- /* "i" is currently the song position (which
- would be equal to the order number in
- no-random mode); convert it to a order
- number, because random mode is enabled */
- i = queue_position_to_order(&playlist->queue, song);
-
- if (!playlist->playing)
- playlist->current = 0;
-
- /* swap the new song with the previous "current" one,
- so playback continues as planned */
- queue_swap_order(&playlist->queue,
- i, playlist->current);
- i = playlist->current;
- }
-
- playlist->stop_on_error = false;
- playlist->error_count = 0;
-
- playlist_play_order(playlist, pc, i);
- return PLAYLIST_RESULT_SUCCESS;
-}
-
-enum playlist_result
-playlist_play_id(struct playlist *playlist, struct player_control *pc,
- int id)
-{
- int song;
-
- if (id == -1) {
- return playlist_play(playlist, pc, id);
- }
-
- song = queue_id_to_position(&playlist->queue, id);
- if (song < 0)
- return PLAYLIST_RESULT_NO_SUCH_SONG;
-
- return playlist_play(playlist, pc, song);
-}
-
-void
-playlist_next(struct playlist *playlist, struct player_control *pc)
-{
- int next_order;
- int current;
-
- if (!playlist->playing)
- return;
-
- assert(!queue_is_empty(&playlist->queue));
- assert(queue_valid_order(&playlist->queue, playlist->current));
-
- current = playlist->current;
- playlist->stop_on_error = false;
-
- /* determine the next song from the queue's order list */
-
- next_order = queue_next_order(&playlist->queue, playlist->current);
- if (next_order < 0) {
- /* no song after this one: stop playback */
- playlist_stop(playlist, pc);
-
- /* reset "current song" */
- playlist->current = -1;
- }
- else
- {
- if (next_order == 0 && playlist->queue.random) {
- /* The queue told us that the next song is the first
- song. This means we are in repeat mode. Shuffle
- the queue order, so this time, the user hears the
- songs in a different than before */
- assert(playlist->queue.repeat);
-
- queue_shuffle_order(&playlist->queue);
-
- /* note that playlist->current and playlist->queued are
- now invalid, but playlist_play_order() will
- discard them anyway */
- }
-
- playlist_play_order(playlist, pc, next_order);
- }
-
- /* Consume mode removes each played songs. */
- if(playlist->queue.consume)
- playlist_delete(playlist, pc,
- queue_order_to_position(&playlist->queue,
- current));
-}
-
-void
-playlist_previous(struct playlist *playlist, struct player_control *pc)
-{
- if (!playlist->playing)
- return;
-
- assert(queue_length(&playlist->queue) > 0);
-
- if (playlist->current > 0) {
- /* play the preceding song */
- playlist_play_order(playlist, pc,
- playlist->current - 1);
- } else if (playlist->queue.repeat) {
- /* play the last song in "repeat" mode */
- playlist_play_order(playlist, pc,
- queue_length(&playlist->queue) - 1);
- } else {
- /* re-start playing the current song if it's
- the first one */
- playlist_play_order(playlist, pc, playlist->current);
- }
-}
-
-enum playlist_result
-playlist_seek_song(struct playlist *playlist, struct player_control *pc,
- unsigned song, float seek_time)
-{
- const struct song *queued;
- unsigned i;
- bool success;
-
- if (!queue_valid_position(&playlist->queue, song))
- return PLAYLIST_RESULT_BAD_RANGE;
-
- queued = playlist_get_queued_song(playlist);
-
- if (playlist->queue.random)
- i = queue_position_to_order(&playlist->queue, song);
- else
- i = song;
-
- pc_clear_error(pc);
- playlist->stop_on_error = true;
- playlist->error_count = 0;
-
- if (!playlist->playing || (unsigned)playlist->current != i) {
- /* seeking is not within the current song - prepare
- song change */
-
- playlist->playing = true;
- playlist->current = i;
-
- queued = NULL;
- }
-
- success = pc_seek(pc, queue_get_order(&playlist->queue, i), seek_time);
- if (!success) {
- playlist_update_queued_song(playlist, pc, queued);
-
- return PLAYLIST_RESULT_NOT_PLAYING;
- }
-
- playlist->queued = -1;
- playlist_update_queued_song(playlist, pc, NULL);
-
- return PLAYLIST_RESULT_SUCCESS;
-}
-
-enum playlist_result
-playlist_seek_song_id(struct playlist *playlist, struct player_control *pc,
- unsigned id, float seek_time)
-{
- int song = queue_id_to_position(&playlist->queue, id);
- if (song < 0)
- return PLAYLIST_RESULT_NO_SUCH_SONG;
-
- return playlist_seek_song(playlist, pc, song, seek_time);
-}
-
-enum playlist_result
-playlist_seek_current(struct playlist *playlist, struct player_control *pc,
- float seek_time, bool relative)
-{
- if (!playlist->playing)
- return PLAYLIST_RESULT_NOT_PLAYING;
-
- if (relative) {
- struct player_status status;
- pc_get_status(pc, &status);
-
- if (status.state != PLAYER_STATE_PLAY &&
- status.state != PLAYER_STATE_PAUSE)
- return PLAYLIST_RESULT_NOT_PLAYING;
-
- seek_time += (int)status.elapsed_time;
- }
-
- if (seek_time < 0)
- seek_time = 0;
-
- return playlist_seek_song(playlist, pc, playlist->current, seek_time);
-}
diff --git a/src/playlist_database.c b/src/playlist_database.c
deleted file mode 100644
index 6b9d87155..000000000
--- a/src/playlist_database.c
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "playlist_database.h"
-#include "playlist_vector.h"
-#include "text_file.h"
-#include "string_util.h"
-
-#include <string.h>
-#include <stdlib.h>
-
-static GQuark
-playlist_database_quark(void)
-{
- return g_quark_from_static_string("playlist_database");
-}
-
-void
-playlist_vector_save(FILE *fp, const struct list_head *pv)
-{
- struct playlist_metadata *pm;
- playlist_vector_for_each(pm, pv)
- fprintf(fp, PLAYLIST_META_BEGIN "%s\n"
- "mtime: %li\n"
- "playlist_end\n",
- pm->name, (long)pm->mtime);
-}
-
-bool
-playlist_metadata_load(FILE *fp, struct list_head *pv, const char *name,
- GString *buffer, GError **error_r)
-{
- struct playlist_metadata pm = {
- .mtime = 0,
- };
- char *line, *colon;
- const char *value;
-
- while ((line = read_text_line(fp, buffer)) != NULL &&
- strcmp(line, "playlist_end") != 0) {
- colon = strchr(line, ':');
- if (colon == NULL || colon == line) {
- g_set_error(error_r, playlist_database_quark(), 0,
- "unknown line in db: %s", line);
- return false;
- }
-
- *colon++ = 0;
- value = strchug_fast_c(colon);
-
- if (strcmp(line, "mtime") == 0)
- pm.mtime = strtol(value, NULL, 10);
- else {
- g_set_error(error_r, playlist_database_quark(), 0,
- "unknown line in db: %s", line);
- return false;
- }
- }
-
- playlist_vector_update_or_add(pv, name, pm.mtime);
- return true;
-}
diff --git a/src/playlist_database.h b/src/playlist_database.h
deleted file mode 100644
index 3238fa06b..000000000
--- a/src/playlist_database.h
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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_PLAYLIST_DATABASE_H
-#define MPD_PLAYLIST_DATABASE_H
-
-#include "check.h"
-
-#include <stdbool.h>
-#include <stdio.h>
-#include <glib.h>
-
-#define PLAYLIST_META_BEGIN "playlist_begin: "
-
-struct list_head;
-
-void
-playlist_vector_save(FILE *fp, const struct list_head *pv);
-
-bool
-playlist_metadata_load(FILE *fp, struct list_head *pv, const char *name,
- GString *buffer, GError **error_r);
-
-#endif
diff --git a/src/playlist_edit.c b/src/playlist_edit.c
deleted file mode 100644
index 8042f2f76..000000000
--- a/src/playlist_edit.c
+++ /dev/null
@@ -1,492 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * Functions for editing the playlist (adding, removing, reordering
- * songs in the queue).
- *
- */
-
-#include "config.h"
-#include "playlist_internal.h"
-#include "player_control.h"
-#include "database.h"
-#include "uri.h"
-#include "song.h"
-#include "idle.h"
-
-#include <stdlib.h>
-
-static void playlist_increment_version(struct playlist *playlist)
-{
- queue_increment_version(&playlist->queue);
-
- idle_add(IDLE_PLAYLIST);
-}
-
-void
-playlist_clear(struct playlist *playlist, struct player_control *pc)
-{
- playlist_stop(playlist, pc);
-
- /* make sure there are no references to allocated songs
- anymore */
- for (unsigned i = 0; i < queue_length(&playlist->queue); i++) {
- const struct song *song = queue_get(&playlist->queue, i);
- if (!song_in_database(song))
- pc_song_deleted(pc, song);
- }
-
- queue_clear(&playlist->queue);
-
- playlist->current = -1;
-
- playlist_increment_version(playlist);
-}
-
-enum playlist_result
-playlist_append_file(struct playlist *playlist, struct player_control *pc,
- const char *path_fs, unsigned *added_id)
-{
- struct song *song = song_file_load(path_fs, NULL);
- if (song == NULL)
- return PLAYLIST_RESULT_NO_SUCH_SONG;
-
- return playlist_append_song(playlist, pc, song, added_id);
-}
-
-enum playlist_result
-playlist_append_song(struct playlist *playlist, struct player_control *pc,
- struct song *song, unsigned *added_id)
-{
- const struct song *queued;
- unsigned id;
-
- if (queue_is_full(&playlist->queue))
- return PLAYLIST_RESULT_TOO_LARGE;
-
- queued = playlist_get_queued_song(playlist);
-
- id = queue_append(&playlist->queue, song, 0);
-
- if (playlist->queue.random) {
- /* shuffle the new song into the list of remaining
- songs to play */
-
- unsigned start;
- if (playlist->queued >= 0)
- start = playlist->queued + 1;
- else
- start = playlist->current + 1;
- if (start < queue_length(&playlist->queue))
- queue_shuffle_order_last(&playlist->queue, start,
- queue_length(&playlist->queue));
- }
-
- playlist_increment_version(playlist);
-
- playlist_update_queued_song(playlist, pc, queued);
-
- if (added_id)
- *added_id = id;
-
- return PLAYLIST_RESULT_SUCCESS;
-}
-
-static struct song *
-song_by_uri(const char *uri)
-{
- struct song *song;
-
- song = db_get_song(uri);
- if (song != NULL)
- return song;
-
- if (uri_has_scheme(uri))
- return song_remote_new(uri);
-
- return NULL;
-}
-
-enum playlist_result
-playlist_append_uri(struct playlist *playlist, struct player_control *pc,
- const char *uri, unsigned *added_id)
-{
- struct song *song;
-
- g_debug("add to playlist: %s", uri);
-
- song = song_by_uri(uri);
- if (song == NULL)
- return PLAYLIST_RESULT_NO_SUCH_SONG;
-
- return playlist_append_song(playlist, pc, song, added_id);
-}
-
-enum playlist_result
-playlist_swap_songs(struct playlist *playlist, struct player_control *pc,
- unsigned song1, unsigned song2)
-{
- const struct song *queued;
-
- if (!queue_valid_position(&playlist->queue, song1) ||
- !queue_valid_position(&playlist->queue, song2))
- return PLAYLIST_RESULT_BAD_RANGE;
-
- queued = playlist_get_queued_song(playlist);
-
- queue_swap(&playlist->queue, song1, song2);
-
- if (playlist->queue.random) {
- /* update the queue order, so that playlist->current
- still points to the current song order */
-
- queue_swap_order(&playlist->queue,
- queue_position_to_order(&playlist->queue,
- song1),
- queue_position_to_order(&playlist->queue,
- song2));
- } else {
- /* correct the "current" song order */
-
- if (playlist->current == (int)song1)
- playlist->current = song2;
- else if (playlist->current == (int)song2)
- playlist->current = song1;
- }
-
- playlist_increment_version(playlist);
-
- playlist_update_queued_song(playlist, pc, queued);
-
- return PLAYLIST_RESULT_SUCCESS;
-}
-
-enum playlist_result
-playlist_swap_songs_id(struct playlist *playlist, struct player_control *pc,
- unsigned id1, unsigned id2)
-{
- int song1 = queue_id_to_position(&playlist->queue, id1);
- int song2 = queue_id_to_position(&playlist->queue, id2);
-
- if (song1 < 0 || song2 < 0)
- return PLAYLIST_RESULT_NO_SUCH_SONG;
-
- return playlist_swap_songs(playlist, pc, song1, song2);
-}
-
-enum playlist_result
-playlist_set_priority(struct playlist *playlist, struct player_control *pc,
- unsigned start, unsigned end,
- uint8_t priority)
-{
- if (start >= queue_length(&playlist->queue))
- return PLAYLIST_RESULT_BAD_RANGE;
-
- if (end > queue_length(&playlist->queue))
- end = queue_length(&playlist->queue);
-
- if (start >= end)
- return PLAYLIST_RESULT_SUCCESS;
-
- /* remember "current" and "queued" */
-
- int current_position = playlist->current >= 0
- ? (int)queue_order_to_position(&playlist->queue,
- playlist->current)
- : -1;
-
- const struct song *queued = playlist_get_queued_song(playlist);
-
- /* apply the priority changes */
-
- queue_set_priority_range(&playlist->queue, start, end, priority,
- playlist->current);
-
- playlist_increment_version(playlist);
-
- /* restore "current" and choose a new "queued" */
-
- if (current_position >= 0)
- playlist->current = queue_position_to_order(&playlist->queue,
- current_position);
-
- playlist_update_queued_song(playlist, pc, queued);
-
- return PLAYLIST_RESULT_SUCCESS;
-}
-
-enum playlist_result
-playlist_set_priority_id(struct playlist *playlist, struct player_control *pc,
- unsigned song_id, uint8_t priority)
-{
- int song_position = queue_id_to_position(&playlist->queue, song_id);
- if (song_position < 0)
- return PLAYLIST_RESULT_NO_SUCH_SONG;
-
- return playlist_set_priority(playlist, pc,
- song_position, song_position + 1,
- priority);
-
-}
-
-static void
-playlist_delete_internal(struct playlist *playlist, struct player_control *pc,
- unsigned song, const struct song **queued_p)
-{
- unsigned songOrder;
-
- assert(song < queue_length(&playlist->queue));
-
- songOrder = queue_position_to_order(&playlist->queue, song);
-
- if (playlist->playing && playlist->current == (int)songOrder) {
- bool paused = pc_get_state(pc) == PLAYER_STATE_PAUSE;
-
- /* the current song is going to be deleted: stop the player */
-
- pc_stop(pc);
- playlist->playing = false;
-
- /* see which song is going to be played instead */
-
- playlist->current = queue_next_order(&playlist->queue,
- playlist->current);
- if (playlist->current == (int)songOrder)
- playlist->current = -1;
-
- if (playlist->current >= 0 && !paused)
- /* play the song after the deleted one */
- playlist_play_order(playlist, pc, playlist->current);
- else
- /* no songs left to play, stop playback
- completely */
- playlist_stop(playlist, pc);
-
- *queued_p = NULL;
- } else if (playlist->current == (int)songOrder)
- /* there's a "current song" but we're not playing
- currently - clear "current" */
- playlist->current = -1;
-
- /* now do it: remove the song */
-
- if (!song_in_database(queue_get(&playlist->queue, song)))
- pc_song_deleted(pc, queue_get(&playlist->queue, song));
-
- queue_delete(&playlist->queue, song);
-
- /* update the "current" and "queued" variables */
-
- if (playlist->current > (int)songOrder) {
- playlist->current--;
- }
-}
-
-enum playlist_result
-playlist_delete(struct playlist *playlist, struct player_control *pc,
- unsigned song)
-{
- const struct song *queued;
-
- if (song >= queue_length(&playlist->queue))
- return PLAYLIST_RESULT_BAD_RANGE;
-
- queued = playlist_get_queued_song(playlist);
-
- playlist_delete_internal(playlist, pc, song, &queued);
-
- playlist_increment_version(playlist);
- playlist_update_queued_song(playlist, pc, queued);
-
- return PLAYLIST_RESULT_SUCCESS;
-}
-
-enum playlist_result
-playlist_delete_range(struct playlist *playlist, struct player_control *pc,
- unsigned start, unsigned end)
-{
- const struct song *queued;
-
- if (start >= queue_length(&playlist->queue))
- return PLAYLIST_RESULT_BAD_RANGE;
-
- if (end > queue_length(&playlist->queue))
- end = queue_length(&playlist->queue);
-
- if (start >= end)
- return PLAYLIST_RESULT_SUCCESS;
-
- queued = playlist_get_queued_song(playlist);
-
- do {
- playlist_delete_internal(playlist, pc, --end, &queued);
- } while (end != start);
-
- playlist_increment_version(playlist);
- playlist_update_queued_song(playlist, pc, queued);
-
- return PLAYLIST_RESULT_SUCCESS;
-}
-
-enum playlist_result
-playlist_delete_id(struct playlist *playlist, struct player_control *pc,
- unsigned id)
-{
- int song = queue_id_to_position(&playlist->queue, id);
- if (song < 0)
- return PLAYLIST_RESULT_NO_SUCH_SONG;
-
- return playlist_delete(playlist, pc, song);
-}
-
-void
-playlist_delete_song(struct playlist *playlist, struct player_control *pc,
- const struct song *song)
-{
- for (int i = queue_length(&playlist->queue) - 1; i >= 0; --i)
- if (song == queue_get(&playlist->queue, i))
- playlist_delete(playlist, pc, i);
-
- pc_song_deleted(pc, song);
-}
-
-enum playlist_result
-playlist_move_range(struct playlist *playlist, struct player_control *pc,
- unsigned start, unsigned end, int to)
-{
- const struct song *queued;
- int currentSong;
-
- if (!queue_valid_position(&playlist->queue, start) ||
- !queue_valid_position(&playlist->queue, end - 1))
- return PLAYLIST_RESULT_BAD_RANGE;
-
- if ((to >= 0 && to + end - start - 1 >= queue_length(&playlist->queue)) ||
- (to < 0 && abs(to) > (int)queue_length(&playlist->queue)))
- return PLAYLIST_RESULT_BAD_RANGE;
-
- if ((int)start == to)
- /* nothing happens */
- return PLAYLIST_RESULT_SUCCESS;
-
- queued = playlist_get_queued_song(playlist);
-
- /*
- * (to < 0) => move to offset from current song
- * (-playlist.length == to) => move to position BEFORE current song
- */
- currentSong = playlist->current >= 0
- ? (int)queue_order_to_position(&playlist->queue,
- playlist->current)
- : -1;
- if (to < 0) {
- if (currentSong < 0)
- /* can't move relative to current song,
- because there is no current song */
- return PLAYLIST_RESULT_BAD_RANGE;
-
- if (start <= (unsigned)currentSong && (unsigned)currentSong < end)
- /* no-op, can't be moved to offset of itself */
- return PLAYLIST_RESULT_SUCCESS;
- to = (currentSong + abs(to)) % queue_length(&playlist->queue);
- if (start < (unsigned)to)
- to--;
- }
-
- queue_move_range(&playlist->queue, start, end, to);
-
- if (!playlist->queue.random) {
- /* update current/queued */
- if ((int)start <= playlist->current &&
- (unsigned)playlist->current < end)
- playlist->current += to - start;
- else if (playlist->current >= (int)end &&
- playlist->current <= to) {
- playlist->current -= end - start;
- } else if (playlist->current >= to &&
- playlist->current < (int)start) {
- playlist->current += end - start;
- }
- }
-
- playlist_increment_version(playlist);
-
- playlist_update_queued_song(playlist, pc, queued);
-
- return PLAYLIST_RESULT_SUCCESS;
-}
-
-enum playlist_result
-playlist_move_id(struct playlist *playlist, struct player_control *pc,
- unsigned id1, int to)
-{
- int song = queue_id_to_position(&playlist->queue, id1);
- if (song < 0)
- return PLAYLIST_RESULT_NO_SUCH_SONG;
-
- return playlist_move_range(playlist, pc, song, song+1, to);
-}
-
-void
-playlist_shuffle(struct playlist *playlist, struct player_control *pc,
- unsigned start, unsigned end)
-{
- const struct song *queued;
-
- if (end > queue_length(&playlist->queue))
- /* correct the "end" offset */
- end = queue_length(&playlist->queue);
-
- if ((start+1) >= end)
- /* needs at least two entries. */
- return;
-
- queued = playlist_get_queued_song(playlist);
- if (playlist->playing && playlist->current >= 0) {
- unsigned current_position;
- current_position = queue_order_to_position(&playlist->queue,
- playlist->current);
-
- if (current_position >= start && current_position < end)
- {
- /* put current playing song first */
- queue_swap(&playlist->queue, start, current_position);
-
- if (playlist->queue.random) {
- playlist->current =
- queue_position_to_order(&playlist->queue, start);
- } else
- playlist->current = start;
-
- /* start shuffle after the current song */
- start++;
- }
- } else {
- /* no playback currently: reset playlist->current */
-
- playlist->current = -1;
- }
-
- queue_shuffle_range(&playlist->queue, start, end);
-
- playlist_increment_version(playlist);
-
- playlist_update_queued_song(playlist, pc, queued);
-}
diff --git a/src/playlist_global.c b/src/playlist_global.c
deleted file mode 100644
index 650b88bb8..000000000
--- a/src/playlist_global.c
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * The manager of the global "struct playlist" instance (g_playlist).
- *
- */
-
-#include "config.h"
-#include "playlist.h"
-#include "playlist_state.h"
-#include "event_pipe.h"
-#include "main.h"
-
-struct playlist g_playlist;
-
-static void
-playlist_tag_event(void)
-{
- playlist_tag_changed(&g_playlist);
-}
-
-static void
-playlist_event(void)
-{
- playlist_sync(&g_playlist, global_player_control);
-}
-
-void
-playlist_global_init(void)
-{
- playlist_init(&g_playlist);
-
- event_pipe_register(PIPE_EVENT_TAG, playlist_tag_event);
- event_pipe_register(PIPE_EVENT_PLAYLIST, playlist_event);
-}
-
-void
-playlist_global_finish(void)
-{
- playlist_finish(&g_playlist);
-}
diff --git a/src/playlist_internal.h b/src/playlist_internal.h
deleted file mode 100644
index 81b175176..000000000
--- a/src/playlist_internal.h
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * Internal header for the components of the playlist code.
- *
- */
-
-#ifndef PLAYLIST_INTERNAL_H
-#define PLAYLIST_INTERNAL_H
-
-#include "playlist.h"
-
-struct player_control;
-
-/**
- * Returns the song object which is currently queued. Returns none if
- * there is none (yet?) or if MPD isn't playing.
- */
-const struct song *
-playlist_get_queued_song(struct playlist *playlist);
-
-/**
- * Updates the "queued song". Calculates the next song according to
- * the current one (if MPD isn't playing, it takes the first song),
- * and queues this song. Clears the old queued song if there was one.
- *
- * @param prev the song which was previously queued, as determined by
- * playlist_get_queued_song()
- */
-void
-playlist_update_queued_song(struct playlist *playlist,
- struct player_control *pc,
- const struct song *prev);
-
-void
-playlist_play_order(struct playlist *playlist, struct player_control *pc,
- int orderNum);
-
-#endif
diff --git a/src/playlist_list.c b/src/playlist_list.c
deleted file mode 100644
index 68d24fe49..000000000
--- a/src/playlist_list.c
+++ /dev/null
@@ -1,348 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "playlist_list.h"
-#include "playlist_plugin.h"
-#include "playlist/extm3u_playlist_plugin.h"
-#include "playlist/m3u_playlist_plugin.h"
-#include "playlist/xspf_playlist_plugin.h"
-#include "playlist/lastfm_playlist_plugin.h"
-#include "playlist/despotify_playlist_plugin.h"
-#include "playlist/soundcloud_playlist_plugin.h"
-#include "playlist/pls_playlist_plugin.h"
-#include "playlist/asx_playlist_plugin.h"
-#include "playlist/rss_playlist_plugin.h"
-#include "playlist/cue_playlist_plugin.h"
-#include "playlist/embcue_playlist_plugin.h"
-#include "input_stream.h"
-#include "uri.h"
-#include "string_util.h"
-#include "conf.h"
-#include "mpd_error.h"
-
-#include <assert.h>
-#include <string.h>
-#include <stdio.h>
-
-const struct playlist_plugin *const playlist_plugins[] = {
- &extm3u_playlist_plugin,
- &m3u_playlist_plugin,
- &xspf_playlist_plugin,
- &pls_playlist_plugin,
- &asx_playlist_plugin,
- &rss_playlist_plugin,
-#ifdef ENABLE_DESPOTIFY
- &despotify_playlist_plugin,
-#endif
-#ifdef ENABLE_LASTFM
- &lastfm_playlist_plugin,
-#endif
-#ifdef ENABLE_SOUNDCLOUD
- &soundcloud_playlist_plugin,
-#endif
- &cue_playlist_plugin,
- &embcue_playlist_plugin,
- NULL
-};
-
-/** which plugins have been initialized successfully? */
-static bool playlist_plugins_enabled[G_N_ELEMENTS(playlist_plugins)];
-
-#define playlist_plugins_for_each_enabled(plugin) \
- playlist_plugins_for_each(plugin) \
- if (playlist_plugins_enabled[playlist_plugin_iterator - playlist_plugins])
-
-/**
- * Find the "playlist" configuration block for the specified plugin.
- *
- * @param plugin_name the name of the playlist plugin
- * @return the configuration block, or NULL if none was configured
- */
-static const struct config_param *
-playlist_plugin_config(const char *plugin_name)
-{
- const struct config_param *param = NULL;
-
- assert(plugin_name != NULL);
-
- while ((param = config_get_next_param(CONF_PLAYLIST_PLUGIN, param)) != NULL) {
- const char *name =
- config_get_block_string(param, "name", NULL);
- if (name == NULL)
- MPD_ERROR("playlist configuration without 'plugin' name in line %d",
- param->line);
-
- if (strcmp(name, plugin_name) == 0)
- return param;
- }
-
- return NULL;
-}
-
-void
-playlist_list_global_init(void)
-{
- for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) {
- const struct playlist_plugin *plugin = playlist_plugins[i];
- const struct config_param *param =
- playlist_plugin_config(plugin->name);
-
- if (!config_get_block_bool(param, "enabled", true))
- /* the plugin is disabled in mpd.conf */
- continue;
-
- playlist_plugins_enabled[i] =
- playlist_plugin_init(playlist_plugins[i], param);
- }
-}
-
-void
-playlist_list_global_finish(void)
-{
- playlist_plugins_for_each_enabled(plugin)
- playlist_plugin_finish(plugin);
-}
-
-static struct playlist_provider *
-playlist_list_open_uri_scheme(const char *uri, GMutex *mutex, GCond *cond,
- bool *tried)
-{
- char *scheme;
- struct playlist_provider *playlist = NULL;
-
- assert(uri != NULL);
-
- scheme = g_uri_parse_scheme(uri);
- if (scheme == NULL)
- return NULL;
-
- for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) {
- const struct playlist_plugin *plugin = playlist_plugins[i];
-
- assert(!tried[i]);
-
- if (playlist_plugins_enabled[i] && plugin->open_uri != NULL &&
- plugin->schemes != NULL &&
- string_array_contains(plugin->schemes, scheme)) {
- playlist = playlist_plugin_open_uri(plugin, uri,
- mutex, cond);
- if (playlist != NULL)
- break;
-
- tried[i] = true;
- }
- }
-
- g_free(scheme);
- return playlist;
-}
-
-static struct playlist_provider *
-playlist_list_open_uri_suffix(const char *uri, GMutex *mutex, GCond *cond,
- const bool *tried)
-{
- const char *suffix;
- struct playlist_provider *playlist = NULL;
-
- assert(uri != NULL);
-
- suffix = uri_get_suffix(uri);
- if (suffix == NULL)
- return NULL;
-
- for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) {
- const struct playlist_plugin *plugin = playlist_plugins[i];
-
- if (playlist_plugins_enabled[i] && !tried[i] &&
- plugin->open_uri != NULL && plugin->suffixes != NULL &&
- string_array_contains(plugin->suffixes, suffix)) {
- playlist = playlist_plugin_open_uri(plugin, uri,
- mutex, cond);
- if (playlist != NULL)
- break;
- }
- }
-
- return playlist;
-}
-
-struct playlist_provider *
-playlist_list_open_uri(const char *uri, GMutex *mutex, GCond *cond)
-{
- struct playlist_provider *playlist;
- /** this array tracks which plugins have already been tried by
- playlist_list_open_uri_scheme() */
- bool tried[G_N_ELEMENTS(playlist_plugins) - 1];
-
- assert(uri != NULL);
-
- memset(tried, false, sizeof(tried));
-
- playlist = playlist_list_open_uri_scheme(uri, mutex, cond, tried);
- if (playlist == NULL)
- playlist = playlist_list_open_uri_suffix(uri, mutex, cond,
- tried);
-
- return playlist;
-}
-
-static struct playlist_provider *
-playlist_list_open_stream_mime2(struct input_stream *is, const char *mime)
-{
- struct playlist_provider *playlist;
-
- assert(is != NULL);
- assert(mime != NULL);
-
- playlist_plugins_for_each_enabled(plugin) {
- if (plugin->open_stream != NULL &&
- plugin->mime_types != NULL &&
- string_array_contains(plugin->mime_types, mime)) {
- /* rewind the stream, so each plugin gets a
- fresh start */
- input_stream_seek(is, 0, SEEK_SET, NULL);
-
- playlist = playlist_plugin_open_stream(plugin, is);
- if (playlist != NULL)
- return playlist;
- }
- }
-
- return NULL;
-}
-
-static struct playlist_provider *
-playlist_list_open_stream_mime(struct input_stream *is)
-{
- assert(is->mime != NULL);
-
- const char *semicolon = strchr(is->mime, ';');
- if (semicolon == NULL)
- return playlist_list_open_stream_mime2(is, is->mime);
-
- if (semicolon == is->mime)
- return NULL;
-
- /* probe only the portion before the semicolon*/
- char *mime = g_strndup(is->mime, semicolon - is->mime);
- struct playlist_provider *playlist =
- playlist_list_open_stream_mime2(is, mime);
- g_free(mime);
- return playlist;
-}
-
-static struct playlist_provider *
-playlist_list_open_stream_suffix(struct input_stream *is, const char *suffix)
-{
- struct playlist_provider *playlist;
-
- assert(is != NULL);
- assert(suffix != NULL);
-
- playlist_plugins_for_each_enabled(plugin) {
- if (plugin->open_stream != NULL &&
- plugin->suffixes != NULL &&
- string_array_contains(plugin->suffixes, suffix)) {
- /* rewind the stream, so each plugin gets a
- fresh start */
- input_stream_seek(is, 0, SEEK_SET, NULL);
-
- playlist = playlist_plugin_open_stream(plugin, is);
- if (playlist != NULL)
- return playlist;
- }
- }
-
- return NULL;
-}
-
-struct playlist_provider *
-playlist_list_open_stream(struct input_stream *is, const char *uri)
-{
- const char *suffix;
- struct playlist_provider *playlist;
-
- input_stream_lock_wait_ready(is);
-
- if (is->mime != NULL) {
- playlist = playlist_list_open_stream_mime(is);
- if (playlist != NULL)
- return playlist;
- }
-
- suffix = uri != NULL ? uri_get_suffix(uri) : NULL;
- if (suffix != NULL) {
- playlist = playlist_list_open_stream_suffix(is, suffix);
- if (playlist != NULL)
- return playlist;
- }
-
- return NULL;
-}
-
-bool
-playlist_suffix_supported(const char *suffix)
-{
- assert(suffix != NULL);
-
- playlist_plugins_for_each_enabled(plugin) {
- if (plugin->suffixes != NULL &&
- string_array_contains(plugin->suffixes, suffix))
- return true;
- }
-
- return false;
-}
-
-struct playlist_provider *
-playlist_list_open_path(const char *path_fs, GMutex *mutex, GCond *cond,
- struct input_stream **is_r)
-{
- GError *error = NULL;
- const char *suffix;
- struct input_stream *is;
- struct playlist_provider *playlist;
-
- assert(path_fs != NULL);
-
- suffix = uri_get_suffix(path_fs);
- if (suffix == NULL || !playlist_suffix_supported(suffix))
- return NULL;
-
- is = input_stream_open(path_fs, mutex, cond, &error);
- if (is == NULL) {
- if (error != NULL) {
- g_warning("%s", error->message);
- g_error_free(error);
- }
-
- return NULL;
- }
-
- input_stream_lock_wait_ready(is);
-
- playlist = playlist_list_open_stream_suffix(is, suffix);
- if (playlist != NULL)
- *is_r = is;
- else
- input_stream_close(is);
-
- return playlist;
-}
diff --git a/src/playlist_list.h b/src/playlist_list.h
deleted file mode 100644
index c3967d5ae..000000000
--- a/src/playlist_list.h
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * 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_PLAYLIST_LIST_H
-#define MPD_PLAYLIST_LIST_H
-
-#include <glib.h>
-
-#include <stdbool.h>
-
-struct playlist_provider;
-struct input_stream;
-
-extern const struct playlist_plugin *const playlist_plugins[];
-
-#define playlist_plugins_for_each(plugin) \
- for (const struct playlist_plugin *plugin, \
- *const*playlist_plugin_iterator = &playlist_plugins[0]; \
- (plugin = *playlist_plugin_iterator) != NULL; \
- ++playlist_plugin_iterator)
-
-/**
- * Initializes all playlist plugins.
- */
-void
-playlist_list_global_init(void);
-
-/**
- * Deinitializes all playlist plugins.
- */
-void
-playlist_list_global_finish(void);
-
-/**
- * Opens a playlist by its URI.
- */
-struct playlist_provider *
-playlist_list_open_uri(const char *uri, GMutex *mutex, GCond *cond);
-
-/**
- * Opens a playlist from an input stream.
- *
- * @param is an #input_stream object which is open and ready
- * @param uri optional URI which was used to open the stream; may be
- * used to select the appropriate playlist plugin
- */
-struct playlist_provider *
-playlist_list_open_stream(struct input_stream *is, const char *uri);
-
-/**
- * Determines if there is a playlist plugin which can handle the
- * specified file name suffix.
- */
-bool
-playlist_suffix_supported(const char *suffix);
-
-/**
- * Opens a playlist from a local file.
- *
- * @param path_fs the path of the playlist file
- * @param is_r on success, an input_stream object is returned here,
- * which must be closed after the playlist_provider object is freed
- * @return a playlist, or NULL on error
- */
-struct playlist_provider *
-playlist_list_open_path(const char *path_fs, GMutex *mutex, GCond *cond,
- struct input_stream **is_r);
-
-#endif
diff --git a/src/playlist_mapper.c b/src/playlist_mapper.c
deleted file mode 100644
index 13adb80d0..000000000
--- a/src/playlist_mapper.c
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "playlist_mapper.h"
-#include "playlist_list.h"
-#include "stored_playlist.h"
-#include "mapper.h"
-#include "uri.h"
-
-#include <assert.h>
-
-static struct playlist_provider *
-playlist_open_path(const char *path_fs, GMutex *mutex, GCond *cond,
- struct input_stream **is_r)
-{
- struct playlist_provider *playlist;
-
- playlist = playlist_list_open_uri(path_fs, mutex, cond);
- if (playlist != NULL)
- *is_r = NULL;
- else
- playlist = playlist_list_open_path(path_fs, mutex, cond, is_r);
-
- return playlist;
-}
-
-/**
- * Load a playlist from the configured playlist directory.
- */
-static struct playlist_provider *
-playlist_open_in_playlist_dir(const char *uri, GMutex *mutex, GCond *cond,
- struct input_stream **is_r)
-{
- char *path_fs;
-
- assert(spl_valid_name(uri));
-
- const char *playlist_directory_fs = map_spl_path();
- if (playlist_directory_fs == NULL)
- return NULL;
-
- path_fs = g_build_filename(playlist_directory_fs, uri, NULL);
-
- struct playlist_provider *playlist =
- playlist_open_path(path_fs, mutex, cond, is_r);
- g_free(path_fs);
-
- return playlist;
-}
-
-/**
- * Load a playlist from the configured music directory.
- */
-static struct playlist_provider *
-playlist_open_in_music_dir(const char *uri, GMutex *mutex, GCond *cond,
- struct input_stream **is_r)
-{
- char *path_fs;
-
- assert(uri_safe_local(uri));
-
- path_fs = map_uri_fs(uri);
- if (path_fs == NULL)
- return NULL;
-
- struct playlist_provider *playlist =
- playlist_open_path(path_fs, mutex, cond, is_r);
- g_free(path_fs);
-
- return playlist;
-}
-
-struct playlist_provider *
-playlist_mapper_open(const char *uri, GMutex *mutex, GCond *cond,
- struct input_stream **is_r)
-{
- struct playlist_provider *playlist;
-
- if (spl_valid_name(uri)) {
- playlist = playlist_open_in_playlist_dir(uri, mutex, cond,
- is_r);
- if (playlist != NULL)
- return playlist;
- }
-
- if (uri_safe_local(uri)) {
- playlist = playlist_open_in_music_dir(uri, mutex, cond, is_r);
- if (playlist != NULL)
- return playlist;
- }
-
- return NULL;
-}
diff --git a/src/playlist_mapper.h b/src/playlist_mapper.h
deleted file mode 100644
index 9a7187d93..000000000
--- a/src/playlist_mapper.h
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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_PLAYLIST_MAPPER_H
-#define MPD_PLAYLIST_MAPPER_H
-
-#include <glib.h>
-
-struct input_stream;
-
-/**
- * Opens a playlist from an URI relative to the playlist or music
- * directory.
- *
- * @param is_r on success, an input_stream object may be returned
- * here, which must be closed after the playlist_provider object is
- * freed
- */
-struct playlist_provider *
-playlist_mapper_open(const char *uri, GMutex *mutex, GCond *cond,
- struct input_stream **is_r);
-
-#endif
diff --git a/src/playlist_plugin.h b/src/playlist_plugin.h
deleted file mode 100644
index a27f651c0..000000000
--- a/src/playlist_plugin.h
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * 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_PLAYLIST_PLUGIN_H
-#define MPD_PLAYLIST_PLUGIN_H
-
-#include <glib.h>
-
-#include <stdbool.h>
-#include <stddef.h>
-
-struct config_param;
-struct input_stream;
-struct tag;
-
-/**
- * An object which provides the contents of a playlist.
- */
-struct playlist_provider {
- const struct playlist_plugin *plugin;
-};
-
-static inline void
-playlist_provider_init(struct playlist_provider *playlist,
- const struct playlist_plugin *plugin)
-{
- playlist->plugin = plugin;
-}
-
-struct playlist_plugin {
- const char *name;
-
- /**
- * Initialize the plugin. Optional method.
- *
- * @param param a configuration block for this plugin, or NULL
- * if none is configured
- * @return true if the plugin was initialized successfully,
- * false if the plugin is not available
- */
- bool (*init)(const struct config_param *param);
-
- /**
- * Deinitialize a plugin which was initialized successfully.
- * Optional method.
- */
- void (*finish)(void);
-
- /**
- * Opens the playlist on the specified URI. This URI has
- * either matched one of the schemes or one of the suffixes.
- */
- struct playlist_provider *(*open_uri)(const char *uri,
- GMutex *mutex, GCond *cond);
-
- /**
- * Opens the playlist in the specified input stream. It has
- * either matched one of the suffixes or one of the MIME
- * types.
- */
- struct playlist_provider *(*open_stream)(struct input_stream *is);
-
- void (*close)(struct playlist_provider *playlist);
-
- struct song *(*read)(struct playlist_provider *playlist);
-
- const char *const*schemes;
- const char *const*suffixes;
- const char *const*mime_types;
-};
-
-/**
- * Initialize a plugin.
- *
- * @param param a configuration block for this plugin, or NULL if none
- * is configured
- * @return true if the plugin was initialized successfully, false if
- * the plugin is not available
- */
-static inline bool
-playlist_plugin_init(const struct playlist_plugin *plugin,
- const struct config_param *param)
-{
- return plugin->init != NULL
- ? plugin->init(param)
- : true;
-}
-
-/**
- * Deinitialize a plugin which was initialized successfully.
- */
-static inline void
-playlist_plugin_finish(const struct playlist_plugin *plugin)
-{
- if (plugin->finish != NULL)
- plugin->finish();
-}
-
-static inline struct playlist_provider *
-playlist_plugin_open_uri(const struct playlist_plugin *plugin, const char *uri,
- GMutex *mutex, GCond *cond)
-{
- return plugin->open_uri(uri, mutex, cond);
-}
-
-static inline struct playlist_provider *
-playlist_plugin_open_stream(const struct playlist_plugin *plugin,
- struct input_stream *is)
-{
- return plugin->open_stream(is);
-}
-
-static inline void
-playlist_plugin_close(struct playlist_provider *playlist)
-{
- playlist->plugin->close(playlist);
-}
-
-static inline struct song *
-playlist_plugin_read(struct playlist_provider *playlist)
-{
- return playlist->plugin->read(playlist);
-}
-
-#endif
diff --git a/src/playlist_print.c b/src/playlist_print.c
deleted file mode 100644
index 59c42f969..000000000
--- a/src/playlist_print.c
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "playlist_print.h"
-#include "playlist_list.h"
-#include "playlist_plugin.h"
-#include "playlist_any.h"
-#include "playlist_song.h"
-#include "playlist.h"
-#include "queue_print.h"
-#include "stored_playlist.h"
-#include "song_print.h"
-#include "song.h"
-#include "database.h"
-#include "client.h"
-#include "input_stream.h"
-
-void
-playlist_print_uris(struct client *client, const struct playlist *playlist)
-{
- const struct queue *queue = &playlist->queue;
-
- queue_print_uris(client, queue, 0, queue_length(queue));
-}
-
-bool
-playlist_print_info(struct client *client, const struct playlist *playlist,
- unsigned start, unsigned end)
-{
- const struct queue *queue = &playlist->queue;
-
- if (end > queue_length(queue))
- /* correct the "end" offset */
- end = queue_length(queue);
-
- if (start > end)
- /* an invalid "start" offset is fatal */
- return false;
-
- queue_print_info(client, queue, start, end);
- return true;
-}
-
-bool
-playlist_print_id(struct client *client, const struct playlist *playlist,
- unsigned id)
-{
- const struct queue *queue = &playlist->queue;
- int position;
-
- position = queue_id_to_position(queue, id);
- if (position < 0)
- /* no such song */
- return false;
-
- return playlist_print_info(client, playlist, position, position + 1);
-}
-
-bool
-playlist_print_current(struct client *client, const struct playlist *playlist)
-{
- int current_position = playlist_get_current_song(playlist);
-
- if (current_position < 0)
- return false;
-
- queue_print_info(client, &playlist->queue,
- current_position, current_position + 1);
- return true;
-}
-
-void
-playlist_print_find(struct client *client, const struct playlist *playlist,
- const struct locate_item_list *list)
-{
- queue_find(client, &playlist->queue, list);
-}
-
-void
-playlist_print_search(struct client *client, const struct playlist *playlist,
- const struct locate_item_list *list)
-{
- queue_search(client, &playlist->queue, list);
-}
-
-void
-playlist_print_changes_info(struct client *client,
- const struct playlist *playlist,
- uint32_t version)
-{
- queue_print_changes_info(client, &playlist->queue, version);
-}
-
-void
-playlist_print_changes_position(struct client *client,
- const struct playlist *playlist,
- uint32_t version)
-{
- queue_print_changes_position(client, &playlist->queue, version);
-}
-
-bool
-spl_print(struct client *client, const char *name_utf8, bool detail,
- GError **error_r)
-{
- GPtrArray *list;
-
- list = spl_load(name_utf8, error_r);
- if (list == NULL)
- return false;
-
- for (unsigned i = 0; i < list->len; ++i) {
- const char *temp = g_ptr_array_index(list, i);
- bool wrote = false;
-
- if (detail) {
- struct song *song = db_get_song(temp);
- if (song) {
- song_print_info(client, song);
- wrote = true;
- }
- }
-
- if (!wrote) {
- client_printf(client, SONG_FILE "%s\n", temp);
- }
- }
-
- spl_free(list);
- return true;
-}
-
-static void
-playlist_provider_print(struct client *client, const char *uri,
- struct playlist_provider *playlist, bool detail)
-{
- struct song *song;
- char *base_uri = uri != NULL ? g_path_get_dirname(uri) : NULL;
-
- while ((song = playlist_plugin_read(playlist)) != NULL) {
- song = playlist_check_translate_song(song, base_uri, false);
- if (song == NULL)
- continue;
-
- if (detail)
- song_print_info(client, song);
- else
- song_print_uri(client, song);
-
- if (!song_in_database(song))
- song_free(song);
- }
-
- g_free(base_uri);
-}
-
-bool
-playlist_file_print(struct client *client, const char *uri, bool detail)
-{
- GMutex *mutex = g_mutex_new();
- GCond *cond = g_cond_new();
-
- struct input_stream *is;
- struct playlist_provider *playlist =
- playlist_open_any(uri, mutex, cond, &is);
- if (playlist == NULL) {
- g_cond_free(cond);
- g_mutex_free(mutex);
- return false;
- }
-
- playlist_provider_print(client, uri, playlist, detail);
- playlist_plugin_close(playlist);
-
- if (is != NULL)
- input_stream_close(is);
-
- g_cond_free(cond);
- g_mutex_free(mutex);
-
- return true;
-}
diff --git a/src/playlist_print.h b/src/playlist_print.h
deleted file mode 100644
index d4f1911d2..000000000
--- a/src/playlist_print.h
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * 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 PLAYLIST_PRINT_H
-#define PLAYLIST_PRINT_H
-
-#include <glib.h>
-#include <stdbool.h>
-#include <stdint.h>
-
-struct client;
-struct playlist;
-struct locate_item_list;
-
-/**
- * Sends the whole playlist to the client, song URIs only.
- */
-void
-playlist_print_uris(struct client *client, const struct playlist *playlist);
-
-/**
- * Sends a range of the playlist to the client, including all known
- * information about the songs. The "end" offset is decreased
- * automatically if it is too large; passing UINT_MAX is allowed.
- * This function however fails when the start offset is invalid.
- */
-bool
-playlist_print_info(struct client *client, const struct playlist *playlist,
- unsigned start, unsigned end);
-
-/**
- * Sends the song with the specified id to the client.
- *
- * @return true on suite, false if there is no such song
- */
-bool
-playlist_print_id(struct client *client, const struct playlist *playlist,
- unsigned id);
-
-/**
- * Sends the current song to the client.
- *
- * @return true on success, false if there is no current song
- */
-bool
-playlist_print_current(struct client *client, const struct playlist *playlist);
-
-/**
- * Find songs in the playlist.
- */
-void
-playlist_print_find(struct client *client, const struct playlist *playlist,
- const struct locate_item_list *list);
-
-/**
- * Search for songs in the playlist.
- */
-void
-playlist_print_search(struct client *client, const struct playlist *playlist,
- const struct locate_item_list *list);
-
-/**
- * Print detailed changes since the specified playlist version.
- */
-void
-playlist_print_changes_info(struct client *client,
- const struct playlist *playlist,
- uint32_t version);
-
-/**
- * Print changes since the specified playlist version, position only.
- */
-void
-playlist_print_changes_position(struct client *client,
- const struct playlist *playlist,
- uint32_t version);
-
-/**
- * Send the stored playlist to the client.
- *
- * @param client the client which requested the playlist
- * @param name_utf8 the name of the stored playlist in UTF-8 encoding
- * @param detail true if all details should be printed
- * @return true on success, false if the playlist does not exist
- */
-bool
-spl_print(struct client *client, const char *name_utf8, bool detail,
- GError **error_r);
-
-/**
- * Send the playlist file to the client.
- *
- * @param client the client which requested the playlist
- * @param uri the URI of the playlist file in UTF-8 encoding
- * @param detail true if all details should be printed
- * @return true on success, false if the playlist does not exist
- */
-bool
-playlist_file_print(struct client *client, const char *uri, bool detail);
-
-#endif
diff --git a/src/playlist_queue.c b/src/playlist_queue.c
deleted file mode 100644
index aada94984..000000000
--- a/src/playlist_queue.c
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "playlist_queue.h"
-#include "playlist_plugin.h"
-#include "playlist_any.h"
-#include "playlist_song.h"
-#include "playlist.h"
-#include "song.h"
-#include "input_stream.h"
-
-enum playlist_result
-playlist_load_into_queue(const char *uri, struct playlist_provider *source,
- unsigned start_index, unsigned end_index,
- struct playlist *dest, struct player_control *pc,
- bool secure)
-{
- enum playlist_result result;
- struct song *song;
- char *base_uri = uri != NULL ? g_path_get_dirname(uri) : NULL;
-
- for (unsigned i = 0;
- i < end_index && (song = playlist_plugin_read(source)) != NULL;
- ++i) {
- if (i < start_index) {
- /* skip songs before the start index */
- if (!song_in_database(song))
- song_free(song);
- continue;
- }
-
- song = playlist_check_translate_song(song, base_uri, secure);
- if (song == NULL)
- continue;
-
- result = playlist_append_song(dest, pc, song, NULL);
- if (result != PLAYLIST_RESULT_SUCCESS) {
- if (!song_in_database(song))
- song_free(song);
- g_free(base_uri);
- return result;
- }
- }
-
- g_free(base_uri);
-
- return PLAYLIST_RESULT_SUCCESS;
-}
-
-enum playlist_result
-playlist_open_into_queue(const char *uri,
- unsigned start_index, unsigned end_index,
- struct playlist *dest, struct player_control *pc,
- bool secure)
-{
- GMutex *mutex = g_mutex_new();
- GCond *cond = g_cond_new();
-
- struct input_stream *is;
- struct playlist_provider *playlist =
- playlist_open_any(uri, mutex, cond, &is);
- if (playlist == NULL) {
- g_cond_free(cond);
- g_mutex_free(mutex);
- return PLAYLIST_RESULT_NO_SUCH_LIST;
- }
-
- enum playlist_result result =
- playlist_load_into_queue(uri, playlist, start_index, end_index,
- dest, pc, secure);
- playlist_plugin_close(playlist);
-
- if (is != NULL)
- input_stream_close(is);
-
- g_cond_free(cond);
- g_mutex_free(mutex);
-
- return result;
-}
diff --git a/src/playlist_queue.h b/src/playlist_queue.h
deleted file mode 100644
index 24a851aab..000000000
--- a/src/playlist_queue.h
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * 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.
- */
-
-/*! \file
- * \brief Glue between playlist plugin and the play queue
- */
-
-#ifndef MPD_PLAYLIST_QUEUE_H
-#define MPD_PLAYLIST_QUEUE_H
-
-#include "playlist_error.h"
-
-#include <stdbool.h>
-
-struct playlist_provider;
-struct playlist;
-struct player_control;
-
-/**
- * Loads the contents of a playlist and append it to the specified
- * play queue.
- *
- * @param uri the URI of the playlist, used to resolve relative song
- * URIs
- * @param start_index the index of the first song
- * @param end_index the index of the last song (excluding)
- */
-enum playlist_result
-playlist_load_into_queue(const char *uri, struct playlist_provider *source,
- unsigned start_index, unsigned end_index,
- struct playlist *dest, struct player_control *pc,
- bool secure);
-
-/**
- * Opens a playlist with a playlist plugin and append to the specified
- * play queue.
- */
-enum playlist_result
-playlist_open_into_queue(const char *uri,
- unsigned start_index, unsigned end_index,
- struct playlist *dest, struct player_control *pc,
- bool secure);
-
-#endif
-
diff --git a/src/playlist_save.c b/src/playlist_save.c
deleted file mode 100644
index 334159e0d..000000000
--- a/src/playlist_save.c
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "playlist_save.h"
-#include "playlist.h"
-#include "stored_playlist.h"
-#include "queue.h"
-#include "song.h"
-#include "mapper.h"
-#include "path.h"
-#include "uri.h"
-#include "database.h"
-#include "idle.h"
-#include "glib_compat.h"
-
-#include <glib.h>
-
-void
-playlist_print_song(FILE *file, const struct song *song)
-{
- if (playlist_saveAbsolutePaths && song_in_database(song)) {
- char *path = map_song_fs(song);
- if (path != NULL) {
- fprintf(file, "%s\n", path);
- g_free(path);
- }
- } else {
- char *uri = song_get_uri(song), *uri_fs;
-
- uri_fs = utf8_to_fs_charset(uri);
- g_free(uri);
-
- fprintf(file, "%s\n", uri_fs);
- g_free(uri_fs);
- }
-}
-
-void
-playlist_print_uri(FILE *file, const char *uri)
-{
- char *s;
-
- if (playlist_saveAbsolutePaths && !uri_has_scheme(uri) &&
- !g_path_is_absolute(uri))
- s = map_uri_fs(uri);
- else
- s = utf8_to_fs_charset(uri);
-
- if (s != NULL) {
- fprintf(file, "%s\n", s);
- g_free(s);
- }
-}
-
-enum playlist_result
-spl_save_queue(const char *name_utf8, const struct queue *queue)
-{
- char *path_fs;
- FILE *file;
-
- if (map_spl_path() == NULL)
- return PLAYLIST_RESULT_DISABLED;
-
- if (!spl_valid_name(name_utf8))
- return PLAYLIST_RESULT_BAD_NAME;
-
- path_fs = map_spl_utf8_to_fs(name_utf8);
- if (path_fs == NULL)
- return PLAYLIST_RESULT_BAD_NAME;
-
- if (g_file_test(path_fs, G_FILE_TEST_EXISTS)) {
- g_free(path_fs);
- return PLAYLIST_RESULT_LIST_EXISTS;
- }
-
- file = fopen(path_fs, "w");
- g_free(path_fs);
-
- if (file == NULL)
- return PLAYLIST_RESULT_ERRNO;
-
- for (unsigned i = 0; i < queue_length(queue); i++)
- playlist_print_song(file, queue_get(queue, i));
-
- fclose(file);
-
- idle_add(IDLE_STORED_PLAYLIST);
- return PLAYLIST_RESULT_SUCCESS;
-}
-
-enum playlist_result
-spl_save_playlist(const char *name_utf8, const struct playlist *playlist)
-{
- return spl_save_queue(name_utf8, &playlist->queue);
-}
-
-bool
-playlist_load_spl(struct playlist *playlist, struct player_control *pc,
- const char *name_utf8,
- unsigned start_index, unsigned end_index,
- GError **error_r)
-{
- GPtrArray *list;
-
- list = spl_load(name_utf8, error_r);
- if (list == NULL)
- return false;
-
- if (list->len < end_index)
- end_index = list->len;
-
- for (unsigned i = start_index; i < end_index; ++i) {
- const char *temp = g_ptr_array_index(list, i);
- if ((playlist_append_uri(playlist, pc, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) {
- /* for windows compatibility, convert slashes */
- char *temp2 = g_strdup(temp);
- char *p = temp2;
- while (*p) {
- if (*p == '\\')
- *p = '/';
- p++;
- }
- if ((playlist_append_uri(playlist, pc, temp2,
- NULL)) != PLAYLIST_RESULT_SUCCESS) {
- g_warning("can't add file \"%s\"", temp2);
- }
- g_free(temp2);
- }
- }
-
- spl_free(list);
- return true;
-}
diff --git a/src/playlist_save.h b/src/playlist_save.h
deleted file mode 100644
index a6c31a9a6..000000000
--- a/src/playlist_save.h
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * 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_PLAYLIST_SAVE_H
-#define MPD_PLAYLIST_SAVE_H
-
-#include "playlist_error.h"
-
-#include <stdbool.h>
-#include <stdio.h>
-
-struct song;
-struct queue;
-struct playlist;
-struct player_control;
-
-void
-playlist_print_song(FILE *fp, const struct song *song);
-
-void
-playlist_print_uri(FILE *fp, const char *uri);
-
-/**
- * Saves a queue object into a stored playlist file.
- */
-enum playlist_result
-spl_save_queue(const char *name_utf8, const struct queue *queue);
-
-/**
- * Saves a playlist object into a stored playlist file.
- */
-enum playlist_result
-spl_save_playlist(const char *name_utf8, const struct playlist *playlist);
-
-/**
- * Loads a stored playlist file, and append all songs to the global
- * playlist.
- */
-bool
-playlist_load_spl(struct playlist *playlist, struct player_control *pc,
- const char *name_utf8,
- unsigned start_index, unsigned end_index,
- GError **error_r);
-
-#endif
diff --git a/src/playlist_song.c b/src/playlist_song.c
deleted file mode 100644
index a3d9ab4d9..000000000
--- a/src/playlist_song.c
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "playlist_song.h"
-#include "database.h"
-#include "mapper.h"
-#include "song.h"
-#include "uri.h"
-#include "path.h"
-#include "ls.h"
-#include "tag.h"
-
-#include <assert.h>
-#include <string.h>
-
-static void
-merge_song_metadata(struct song *dest, const struct song *base,
- const struct song *add)
-{
- dest->tag = base->tag != NULL
- ? (add->tag != NULL
- ? tag_merge(base->tag, add->tag)
- : tag_dup(base->tag))
- : (add->tag != NULL
- ? tag_dup(add->tag)
- : NULL);
-
- dest->mtime = base->mtime;
- dest->start_ms = add->start_ms;
- dest->end_ms = add->end_ms;
-}
-
-static struct song *
-apply_song_metadata(struct song *dest, const struct song *src)
-{
- struct song *tmp;
-
- assert(dest != NULL);
- assert(src != NULL);
-
- if (src->tag == NULL && src->start_ms == 0 && src->end_ms == 0)
- return dest;
-
- if (song_in_database(dest)) {
- char *path_fs = map_song_fs(dest);
- if (path_fs == NULL)
- return dest;
-
- char *path_utf8 = fs_charset_to_utf8(path_fs);
- if (path_utf8 != NULL)
- g_free(path_fs);
- else
- path_utf8 = path_fs;
-
- tmp = song_file_new(path_utf8, NULL);
- g_free(path_utf8);
-
- merge_song_metadata(tmp, dest, src);
- } else {
- tmp = song_file_new(dest->uri, NULL);
- merge_song_metadata(tmp, dest, src);
- }
-
- if (dest->tag != NULL && dest->tag->time > 0 &&
- src->start_ms > 0 && src->end_ms == 0 &&
- src->start_ms / 1000 < (unsigned)dest->tag->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;
-
- if (!song_in_database(dest))
- song_free(dest);
-
- return tmp;
-}
-
-static struct song *
-playlist_check_load_song(const struct song *song, const char *uri, bool secure)
-{
- struct song *dest;
-
- if (uri_has_scheme(uri)) {
- dest = song_remote_new(uri);
- } else if (g_path_is_absolute(uri) && secure) {
- dest = song_file_load(uri, NULL);
- if (dest == NULL)
- return NULL;
- } else {
- dest = db_get_song(uri);
- if (dest == NULL)
- /* not found in database */
- return NULL;
- }
-
- return apply_song_metadata(dest, song);
-}
-
-struct song *
-playlist_check_translate_song(struct song *song, const char *base_uri,
- bool secure)
-{
- if (song_in_database(song))
- /* already ok */
- return song;
-
- const char *uri = song->uri;
-
- if (uri_has_scheme(uri)) {
- if (uri_supported_scheme(uri))
- /* valid remote song */
- return song;
- else {
- /* unsupported remote song */
- song_free(song);
- return NULL;
- }
- }
-
- if (base_uri != NULL && strcmp(base_uri, ".") == 0)
- /* g_path_get_dirname() returns "." when there is no
- directory name in the given path; clear that now,
- because it would break the database lookup
- functions */
- base_uri = NULL;
-
- if (g_path_is_absolute(uri)) {
- /* XXX fs_charset vs utf8? */
- const char *suffix = map_to_relative_path(uri);
- assert(suffix != NULL);
-
- if (suffix != uri)
- uri = suffix;
- else if (!secure) {
- /* local files must be relative to the music
- directory when "secure" is enabled */
- song_free(song);
- return NULL;
- }
-
- base_uri = NULL;
- }
-
- char *allocated = NULL;
- if (base_uri != NULL)
- uri = allocated = g_build_filename(base_uri, uri, NULL);
-
- struct song *dest = playlist_check_load_song(song, uri, secure);
- song_free(song);
- g_free(allocated);
- return dest;
-}
diff --git a/src/playlist_song.h b/src/playlist_song.h
deleted file mode 100644
index ea8786912..000000000
--- a/src/playlist_song.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * 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_PLAYLIST_SONG_H
-#define MPD_PLAYLIST_SONG_H
-
-#include <stdbool.h>
-
-/**
- * Verifies the song, returns NULL if it is unsafe. Translate the
- * song to a new song object within the database, if it is a local
- * file. The old song object is freed.
- *
- * @param secure if true, then local files are only allowed if they
- * are relative to base_uri
- */
-struct song *
-playlist_check_translate_song(struct song *song, const char *base_uri,
- bool secure);
-
-#endif
diff --git a/src/playlist_state.c b/src/playlist_state.c
deleted file mode 100644
index 4aa2c2c92..000000000
--- a/src/playlist_state.c
+++ /dev/null
@@ -1,250 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * Saving and loading the playlist to/from the state file.
- *
- */
-
-#include "config.h"
-#include "playlist_state.h"
-#include "playlist.h"
-#include "player_control.h"
-#include "queue_save.h"
-#include "path.h"
-#include "text_file.h"
-#include "conf.h"
-
-#include <string.h>
-#include <stdlib.h>
-
-#define PLAYLIST_STATE_FILE_STATE "state: "
-#define PLAYLIST_STATE_FILE_RANDOM "random: "
-#define PLAYLIST_STATE_FILE_REPEAT "repeat: "
-#define PLAYLIST_STATE_FILE_SINGLE "single: "
-#define PLAYLIST_STATE_FILE_CONSUME "consume: "
-#define PLAYLIST_STATE_FILE_CURRENT "current: "
-#define PLAYLIST_STATE_FILE_TIME "time: "
-#define PLAYLIST_STATE_FILE_CROSSFADE "crossfade: "
-#define PLAYLIST_STATE_FILE_MIXRAMPDB "mixrampdb: "
-#define PLAYLIST_STATE_FILE_MIXRAMPDELAY "mixrampdelay: "
-#define PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "playlist_begin"
-#define PLAYLIST_STATE_FILE_PLAYLIST_END "playlist_end"
-
-#define PLAYLIST_STATE_FILE_STATE_PLAY "play"
-#define PLAYLIST_STATE_FILE_STATE_PAUSE "pause"
-#define PLAYLIST_STATE_FILE_STATE_STOP "stop"
-
-#define PLAYLIST_BUFFER_SIZE 2*MPD_PATH_MAX
-
-void
-playlist_state_save(FILE *fp, const struct playlist *playlist,
- struct player_control *pc)
-{
- struct player_status player_status;
-
- pc_get_status(pc, &player_status);
-
- fputs(PLAYLIST_STATE_FILE_STATE, fp);
-
- if (playlist->playing) {
- switch (player_status.state) {
- case PLAYER_STATE_PAUSE:
- fputs(PLAYLIST_STATE_FILE_STATE_PAUSE "\n", fp);
- break;
- default:
- fputs(PLAYLIST_STATE_FILE_STATE_PLAY "\n", fp);
- }
- fprintf(fp, PLAYLIST_STATE_FILE_CURRENT "%i\n",
- queue_order_to_position(&playlist->queue,
- playlist->current));
- fprintf(fp, PLAYLIST_STATE_FILE_TIME "%i\n",
- (int)player_status.elapsed_time);
- } else {
- fputs(PLAYLIST_STATE_FILE_STATE_STOP "\n", fp);
-
- if (playlist->current >= 0)
- fprintf(fp, PLAYLIST_STATE_FILE_CURRENT "%i\n",
- queue_order_to_position(&playlist->queue,
- playlist->current));
- }
-
- fprintf(fp, PLAYLIST_STATE_FILE_RANDOM "%i\n", playlist->queue.random);
- fprintf(fp, PLAYLIST_STATE_FILE_REPEAT "%i\n", playlist->queue.repeat);
- fprintf(fp, PLAYLIST_STATE_FILE_SINGLE "%i\n", playlist->queue.single);
- fprintf(fp, PLAYLIST_STATE_FILE_CONSUME "%i\n",
- playlist->queue.consume);
- fprintf(fp, PLAYLIST_STATE_FILE_CROSSFADE "%i\n",
- (int)(pc_get_cross_fade(pc)));
- fprintf(fp, PLAYLIST_STATE_FILE_MIXRAMPDB "%f\n",
- pc_get_mixramp_db(pc));
- fprintf(fp, PLAYLIST_STATE_FILE_MIXRAMPDELAY "%f\n",
- pc_get_mixramp_delay(pc));
- fputs(PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "\n", fp);
- queue_save(fp, &playlist->queue);
- fputs(PLAYLIST_STATE_FILE_PLAYLIST_END "\n", fp);
-}
-
-static void
-playlist_state_load(FILE *fp, GString *buffer, struct playlist *playlist)
-{
- const char *line = read_text_line(fp, buffer);
- if (line == NULL) {
- g_warning("No playlist in state file");
- return;
- }
-
- while (!g_str_has_prefix(line, PLAYLIST_STATE_FILE_PLAYLIST_END)) {
- queue_load_song(fp, buffer, line, &playlist->queue);
-
- line = read_text_line(fp, buffer);
- if (line == NULL) {
- g_warning("'" PLAYLIST_STATE_FILE_PLAYLIST_END
- "' not found in state file");
- break;
- }
- }
-
- queue_increment_version(&playlist->queue);
-}
-
-bool
-playlist_state_restore(const char *line, FILE *fp, GString *buffer,
- struct playlist *playlist, struct player_control *pc)
-{
- int current = -1;
- int seek_time = 0;
- enum player_state state = PLAYER_STATE_STOP;
- bool random_mode = false;
-
- if (!g_str_has_prefix(line, PLAYLIST_STATE_FILE_STATE))
- return false;
-
- line += sizeof(PLAYLIST_STATE_FILE_STATE) - 1;
-
- if (strcmp(line, PLAYLIST_STATE_FILE_STATE_PLAY) == 0)
- state = PLAYER_STATE_PLAY;
- else if (strcmp(line, PLAYLIST_STATE_FILE_STATE_PAUSE) == 0)
- state = PLAYER_STATE_PAUSE;
-
- while ((line = read_text_line(fp, buffer)) != NULL) {
- if (g_str_has_prefix(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)) {
- if (strcmp
- (&(line[strlen(PLAYLIST_STATE_FILE_REPEAT)]),
- "1") == 0) {
- playlist_set_repeat(playlist, pc, true);
- } else
- playlist_set_repeat(playlist, pc, false);
- } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_SINGLE)) {
- if (strcmp
- (&(line[strlen(PLAYLIST_STATE_FILE_SINGLE)]),
- "1") == 0) {
- playlist_set_single(playlist, pc, true);
- } else
- playlist_set_single(playlist, pc, false);
- } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_CONSUME)) {
- if (strcmp
- (&(line[strlen(PLAYLIST_STATE_FILE_CONSUME)]),
- "1") == 0) {
- playlist_set_consume(playlist, true);
- } else
- playlist_set_consume(playlist, false);
- } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_CROSSFADE)) {
- pc_set_cross_fade(pc,
- atoi(line + strlen(PLAYLIST_STATE_FILE_CROSSFADE)));
- } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_MIXRAMPDB)) {
- pc_set_mixramp_db(pc,
- atof(line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDB)));
- } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_MIXRAMPDELAY)) {
- pc_set_mixramp_delay(pc,
- atof(line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDELAY)));
- } else if (g_str_has_prefix(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)) {
- current = atoi(&(line
- [strlen
- (PLAYLIST_STATE_FILE_CURRENT)]));
- } else if (g_str_has_prefix(line,
- PLAYLIST_STATE_FILE_PLAYLIST_BEGIN)) {
- playlist_state_load(fp, buffer, playlist);
- }
- }
-
- playlist_set_random(playlist, pc, random_mode);
-
- if (!queue_is_empty(&playlist->queue)) {
- if (!queue_valid_position(&playlist->queue, current))
- current = 0;
-
- if (state == PLAYER_STATE_PLAY &&
- config_get_bool("restore_paused", false))
- /* the user doesn't want MPD to auto-start
- playback after startup; fall back to
- "pause" */
- state = PLAYER_STATE_PAUSE;
-
- /* enable all devices for the first time; this must be
- called here, after the audio output states were
- restored, before playback begins */
- if (state != PLAYER_STATE_STOP)
- pc_update_audio(pc);
-
- if (state == PLAYER_STATE_STOP /* && config_option */)
- playlist->current = current;
- else if (seek_time == 0)
- playlist_play(playlist, pc, current);
- else
- playlist_seek_song(playlist, pc, current, seek_time);
-
- if (state == PLAYER_STATE_PAUSE)
- pc_pause(pc);
- }
-
- return true;
-}
-
-unsigned
-playlist_state_get_hash(const struct playlist *playlist,
- struct player_control *pc)
-{
- struct player_status player_status;
-
- pc_get_status(pc, &player_status);
-
- return playlist->queue.version ^
- (player_status.state != PLAYER_STATE_STOP
- ? ((int)player_status.elapsed_time << 8)
- : 0) ^
- (playlist->current >= 0
- ? (queue_order_to_position(&playlist->queue,
- playlist->current) << 16)
- : 0) ^
- ((int)pc_get_cross_fade(pc) << 20) ^
- (player_status.state << 24) ^
- (playlist->queue.random << 27) ^
- (playlist->queue.repeat << 28) ^
- (playlist->queue.single << 29) ^
- (playlist->queue.consume << 30) ^
- (playlist->queue.random << 31);
-}
diff --git a/src/playlist_state.h b/src/playlist_state.h
deleted file mode 100644
index f67d01d2c..000000000
--- a/src/playlist_state.h
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * Saving and loading the playlist to/from the state file.
- *
- */
-
-#ifndef PLAYLIST_STATE_H
-#define PLAYLIST_STATE_H
-
-#include <glib.h>
-#include <stdbool.h>
-#include <stdio.h>
-
-struct playlist;
-struct player_control;
-
-void
-playlist_state_save(FILE *fp, const struct playlist *playlist,
- struct player_control *pc);
-
-bool
-playlist_state_restore(const char *line, FILE *fp, GString *buffer,
- struct playlist *playlist, struct player_control *pc);
-
-/**
- * Generates a hash number for the current state of the playlist and
- * the playback options. This is used by timer_save_state_file() to
- * determine whether the state has changed and the state file should
- * be saved.
- */
-unsigned
-playlist_state_get_hash(const struct playlist *playlist,
- struct player_control *pc);
-
-#endif
diff --git a/src/playlist_vector.c b/src/playlist_vector.c
deleted file mode 100644
index 74c7bf089..000000000
--- a/src/playlist_vector.c
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "playlist_vector.h"
-#include "db_lock.h"
-
-#include <assert.h>
-#include <string.h>
-#include <glib.h>
-
-static struct playlist_metadata *
-playlist_metadata_new(const char *name, time_t mtime)
-{
- assert(name != NULL);
-
- struct playlist_metadata *pm = g_slice_new(struct playlist_metadata);
- pm->name = g_strdup(name);
- pm->mtime = mtime;
- return pm;
-}
-
-static void
-playlist_metadata_free(struct playlist_metadata *pm)
-{
- assert(pm != NULL);
- assert(pm->name != NULL);
-
- g_free(pm->name);
- g_slice_free(struct playlist_metadata, pm);
-}
-
-void
-playlist_vector_deinit(struct list_head *pv)
-{
- assert(pv != NULL);
-
- struct playlist_metadata *pm, *n;
- playlist_vector_for_each_safe(pm, n, pv)
- playlist_metadata_free(pm);
-}
-
-struct playlist_metadata *
-playlist_vector_find(struct list_head *pv, const char *name)
-{
- assert(holding_db_lock());
- assert(pv != NULL);
- assert(name != NULL);
-
- struct playlist_metadata *pm;
- playlist_vector_for_each(pm, pv)
- if (strcmp(pm->name, name) == 0)
- return pm;
-
- return NULL;
-}
-
-void
-playlist_vector_add(struct list_head *pv,
- const char *name, time_t mtime)
-{
- assert(holding_db_lock());
-
- struct playlist_metadata *pm = playlist_metadata_new(name, mtime);
- list_add_tail(&pm->siblings, pv);
-}
-
-bool
-playlist_vector_update_or_add(struct list_head *pv,
- const char *name, time_t mtime)
-{
- assert(holding_db_lock());
-
- struct playlist_metadata *pm = playlist_vector_find(pv, name);
- if (pm != NULL) {
- if (mtime == pm->mtime)
- return false;
-
- pm->mtime = mtime;
- } else
- playlist_vector_add(pv, name, mtime);
-
- return true;
-}
-
-bool
-playlist_vector_remove(struct list_head *pv, const char *name)
-{
- assert(holding_db_lock());
-
- struct playlist_metadata *pm = playlist_vector_find(pv, name);
- if (pm == NULL)
- return false;
-
- list_del(&pm->siblings);
- playlist_metadata_free(pm);
- return true;
-}
diff --git a/src/playlist_vector.h b/src/playlist_vector.h
deleted file mode 100644
index 0af6df8b4..000000000
--- a/src/playlist_vector.h
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * 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_PLAYLIST_VECTOR_H
-#define MPD_PLAYLIST_VECTOR_H
-
-#include "util/list.h"
-
-#include <stdbool.h>
-#include <stddef.h>
-#include <sys/time.h>
-
-#define playlist_vector_for_each(pos, head) \
- list_for_each_entry(pos, head, siblings)
-
-#define playlist_vector_for_each_safe(pos, n, head) \
- list_for_each_entry_safe(pos, n, head, siblings)
-
-/**
- * A directory entry pointing to a playlist file.
- */
-struct playlist_metadata {
- struct list_head siblings;
-
- /**
- * The UTF-8 encoded name of the playlist file.
- */
- char *name;
-
- time_t mtime;
-};
-
-void
-playlist_vector_deinit(struct list_head *pv);
-
-/**
- * Caller must lock the #db_mutex.
- */
-struct playlist_metadata *
-playlist_vector_find(struct list_head *pv, const char *name);
-
-/**
- * Caller must lock the #db_mutex.
- */
-void
-playlist_vector_add(struct list_head *pv,
- const char *name, time_t mtime);
-
-/**
- * Caller must lock the #db_mutex.
- *
- * @return true if the vector or one of its items was modified
- */
-bool
-playlist_vector_update_or_add(struct list_head *pv,
- const char *name, time_t mtime);
-
-/**
- * Caller must lock the #db_mutex.
- */
-bool
-playlist_vector_remove(struct list_head *pv, const char *name);
-
-#endif /* SONGVEC_H */
diff --git a/src/protocol/ArgParser.cxx b/src/protocol/ArgParser.cxx
new file mode 100644
index 000000000..6bd53a358
--- /dev/null
+++ b/src/protocol/ArgParser.cxx
@@ -0,0 +1,191 @@
+/*
+ * 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 "ArgParser.hxx"
+#include "Result.hxx"
+
+#include <glib.h>
+#include <stdlib.h>
+
+bool
+check_uint32(Client *client, uint32_t *dst, const char *s)
+{
+ char *test;
+
+ *dst = strtoul(s, &test, 10);
+ if (test == s || *test != '\0') {
+ command_error(client, ACK_ERROR_ARG,
+ "Integer expected: %s", s);
+ return false;
+ }
+ return true;
+}
+
+bool
+check_int(Client *client, int *value_r, const char *s)
+{
+ char *test;
+ long value;
+
+ value = strtol(s, &test, 10);
+ if (test == s || *test != '\0') {
+ command_error(client, ACK_ERROR_ARG,
+ "Integer expected: %s", s);
+ return false;
+ }
+
+#if G_MAXLONG > G_MAXINT
+ if (value < G_MININT || value > G_MAXINT) {
+ command_error(client, ACK_ERROR_ARG,
+ "Number too large: %s", s);
+ return false;
+ }
+#endif
+
+ *value_r = (int)value;
+ return true;
+}
+
+bool
+check_range(Client *client, unsigned *value_r1, unsigned *value_r2,
+ const char *s)
+{
+ char *test, *test2;
+ long value;
+
+ value = strtol(s, &test, 10);
+ if (test == s || (*test != '\0' && *test != ':')) {
+ command_error(client, ACK_ERROR_ARG,
+ "Integer or range expected: %s", s);
+ return false;
+ }
+
+ if (value == -1 && *test == 0) {
+ /* compatibility with older MPD versions: specifying
+ "-1" makes MPD display the whole list */
+ *value_r1 = 0;
+ *value_r2 = G_MAXUINT;
+ return true;
+ }
+
+ if (value < 0) {
+ command_error(client, ACK_ERROR_ARG,
+ "Number is negative: %s", s);
+ return false;
+ }
+
+#if G_MAXLONG > G_MAXUINT
+ if (value > G_MAXUINT) {
+ command_error(client, ACK_ERROR_ARG,
+ "Number too large: %s", s);
+ return false;
+ }
+#endif
+
+ *value_r1 = (unsigned)value;
+
+ if (*test == ':') {
+ value = strtol(++test, &test2, 10);
+ if (*test2 != '\0') {
+ command_error(client, ACK_ERROR_ARG,
+ "Integer or range expected: %s", s);
+ return false;
+ }
+
+ if (test == test2)
+ value = G_MAXUINT;
+
+ if (value < 0) {
+ command_error(client, ACK_ERROR_ARG,
+ "Number is negative: %s", s);
+ return false;
+ }
+
+#if G_MAXLONG > G_MAXUINT
+ if (value > G_MAXUINT) {
+ command_error(client, ACK_ERROR_ARG,
+ "Number too large: %s", s);
+ return false;
+ }
+#endif
+ *value_r2 = (unsigned)value;
+ } else {
+ *value_r2 = (unsigned)value + 1;
+ }
+
+ return true;
+}
+
+bool
+check_unsigned(Client *client, unsigned *value_r, const char *s)
+{
+ unsigned long value;
+ char *endptr;
+
+ value = strtoul(s, &endptr, 10);
+ if (endptr == s || *endptr != 0) {
+ command_error(client, ACK_ERROR_ARG,
+ "Integer expected: %s", s);
+ return false;
+ }
+
+ if (value > G_MAXUINT) {
+ command_error(client, ACK_ERROR_ARG,
+ "Number too large: %s", s);
+ return false;
+ }
+
+ *value_r = (unsigned)value;
+ return true;
+}
+
+bool
+check_bool(Client *client, bool *value_r, const char *s)
+{
+ long value;
+ char *endptr;
+
+ value = strtol(s, &endptr, 10);
+ if (endptr == s || *endptr != 0 || (value != 0 && value != 1)) {
+ command_error(client, ACK_ERROR_ARG,
+ "Boolean (0/1) expected: %s", s);
+ return false;
+ }
+
+ *value_r = !!value;
+ return true;
+}
+
+bool
+check_float(Client *client, float *value_r, const char *s)
+{
+ float value;
+ char *endptr;
+
+ value = strtof(s, &endptr);
+ if (endptr == s || *endptr != 0) {
+ command_error(client, ACK_ERROR_ARG,
+ "Float expected: %s", s);
+ return false;
+ }
+
+ *value_r = value;
+ return true;
+}
diff --git a/src/protocol/ArgParser.hxx b/src/protocol/ArgParser.hxx
new file mode 100644
index 000000000..f69248d2d
--- /dev/null
+++ b/src/protocol/ArgParser.hxx
@@ -0,0 +1,48 @@
+/*
+ * 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_PROTOCOL_ARGPARSER_HXX
+#define MPD_PROTOCOL_ARGPARSER_HXX
+
+#include "check.h"
+
+#include <stdint.h>
+
+class Client;
+
+bool
+check_uint32(Client *client, uint32_t *dst, const char *s);
+
+bool
+check_int(Client *client, int *value_r, const char *s);
+
+bool
+check_range(Client *client, unsigned *value_r1, unsigned *value_r2,
+ const char *s);
+
+bool
+check_unsigned(Client *client, unsigned *value_r, const char *s);
+
+bool
+check_bool(Client *client, bool *value_r, const char *s);
+
+bool
+check_float(Client *client, float *value_r, const char *s);
+
+#endif
diff --git a/src/protocol/Result.cxx b/src/protocol/Result.cxx
new file mode 100644
index 000000000..e10a731cc
--- /dev/null
+++ b/src/protocol/Result.cxx
@@ -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.
+ */
+
+#include "config.h"
+#include "Result.hxx"
+#include "Client.hxx"
+
+#include <assert.h>
+
+const char *current_command;
+int command_list_num;
+
+void
+command_success(Client *client)
+{
+ client_puts(client, "OK\n");
+}
+
+void
+command_error_v(Client *client, enum ack error,
+ const char *fmt, va_list args)
+{
+ assert(client != NULL);
+ assert(current_command != NULL);
+
+ client_printf(client, "ACK [%i@%i] {%s} ",
+ (int)error, command_list_num, current_command);
+ client_vprintf(client, fmt, args);
+ client_puts(client, "\n");
+
+ current_command = NULL;
+}
+
+void
+command_error(Client *client, enum ack error, const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ command_error_v(client, error, fmt, args);
+ va_end(args);
+}
diff --git a/src/protocol/Result.hxx b/src/protocol/Result.hxx
new file mode 100644
index 000000000..99d9a2fa0
--- /dev/null
+++ b/src/protocol/Result.hxx
@@ -0,0 +1,43 @@
+/*
+ * 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_PROTOCOL_RESULT_HXX
+#define MPD_PROTOCOL_RESULT_HXX
+
+#include "check.h"
+#include "gcc.h"
+#include "ack.h"
+
+class Client;
+
+extern const char *current_command;
+extern int command_list_num;
+
+void
+command_success(Client *client);
+
+void
+command_error_v(Client *client, enum ack error,
+ const char *fmt, va_list args);
+
+gcc_fprintf_
+void
+command_error(Client *client, enum ack error, const char *fmt, ...);
+
+#endif
diff --git a/src/protocol/argparser.c b/src/protocol/argparser.c
deleted file mode 100644
index d20437cb7..000000000
--- a/src/protocol/argparser.c
+++ /dev/null
@@ -1,191 +0,0 @@
-/*
- * 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 "argparser.h"
-#include "result.h"
-
-#include <glib.h>
-#include <stdlib.h>
-
-bool
-check_uint32(struct client *client, uint32_t *dst, const char *s)
-{
- char *test;
-
- *dst = strtoul(s, &test, 10);
- if (test == s || *test != '\0') {
- command_error(client, ACK_ERROR_ARG,
- "Integer expected: %s", s);
- return false;
- }
- return true;
-}
-
-bool
-check_int(struct client *client, int *value_r, const char *s)
-{
- char *test;
- long value;
-
- value = strtol(s, &test, 10);
- if (test == s || *test != '\0') {
- command_error(client, ACK_ERROR_ARG,
- "Integer expected: %s", s);
- return false;
- }
-
-#if G_MAXLONG > G_MAXINT
- if (value < G_MININT || value > G_MAXINT) {
- command_error(client, ACK_ERROR_ARG,
- "Number too large: %s", s);
- return false;
- }
-#endif
-
- *value_r = (int)value;
- return true;
-}
-
-bool
-check_range(struct client *client, unsigned *value_r1, unsigned *value_r2,
- const char *s)
-{
- char *test, *test2;
- long value;
-
- value = strtol(s, &test, 10);
- if (test == s || (*test != '\0' && *test != ':')) {
- command_error(client, ACK_ERROR_ARG,
- "Integer or range expected: %s", s);
- return false;
- }
-
- if (value == -1 && *test == 0) {
- /* compatibility with older MPD versions: specifying
- "-1" makes MPD display the whole list */
- *value_r1 = 0;
- *value_r2 = G_MAXUINT;
- return true;
- }
-
- if (value < 0) {
- command_error(client, ACK_ERROR_ARG,
- "Number is negative: %s", s);
- return false;
- }
-
-#if G_MAXLONG > G_MAXUINT
- if (value > G_MAXUINT) {
- command_error(client, ACK_ERROR_ARG,
- "Number too large: %s", s);
- return false;
- }
-#endif
-
- *value_r1 = (unsigned)value;
-
- if (*test == ':') {
- value = strtol(++test, &test2, 10);
- if (*test2 != '\0') {
- command_error(client, ACK_ERROR_ARG,
- "Integer or range expected: %s", s);
- return false;
- }
-
- if (test == test2)
- value = G_MAXUINT;
-
- if (value < 0) {
- command_error(client, ACK_ERROR_ARG,
- "Number is negative: %s", s);
- return false;
- }
-
-#if G_MAXLONG > G_MAXUINT
- if (value > G_MAXUINT) {
- command_error(client, ACK_ERROR_ARG,
- "Number too large: %s", s);
- return false;
- }
-#endif
- *value_r2 = (unsigned)value;
- } else {
- *value_r2 = (unsigned)value + 1;
- }
-
- return true;
-}
-
-bool
-check_unsigned(struct client *client, unsigned *value_r, const char *s)
-{
- unsigned long value;
- char *endptr;
-
- value = strtoul(s, &endptr, 10);
- if (endptr == s || *endptr != 0) {
- command_error(client, ACK_ERROR_ARG,
- "Integer expected: %s", s);
- return false;
- }
-
- if (value > G_MAXUINT) {
- command_error(client, ACK_ERROR_ARG,
- "Number too large: %s", s);
- return false;
- }
-
- *value_r = (unsigned)value;
- return true;
-}
-
-bool
-check_bool(struct client *client, bool *value_r, const char *s)
-{
- long value;
- char *endptr;
-
- value = strtol(s, &endptr, 10);
- if (endptr == s || *endptr != 0 || (value != 0 && value != 1)) {
- command_error(client, ACK_ERROR_ARG,
- "Boolean (0/1) expected: %s", s);
- return false;
- }
-
- *value_r = !!value;
- return true;
-}
-
-bool
-check_float(struct client *client, float *value_r, const char *s)
-{
- float value;
- char *endptr;
-
- value = strtof(s, &endptr);
- if (endptr == s || *endptr != 0) {
- command_error(client, ACK_ERROR_ARG,
- "Float expected: %s", s);
- return false;
- }
-
- *value_r = value;
- return true;
-}
diff --git a/src/protocol/argparser.h b/src/protocol/argparser.h
deleted file mode 100644
index e88aea478..000000000
--- a/src/protocol/argparser.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef MPD_PROTOCOL_ARGPARSER_H
-#define MPD_PROTOCOL_ARGPARSER_H
-
-#include "check.h"
-
-#include <stdbool.h>
-#include <stdint.h>
-
-struct client;
-
-bool
-check_uint32(struct client *client, uint32_t *dst, const char *s);
-
-bool
-check_int(struct client *client, int *value_r, const char *s);
-
-bool
-check_range(struct client *client, unsigned *value_r1, unsigned *value_r2,
- const char *s);
-
-bool
-check_unsigned(struct client *client, unsigned *value_r, const char *s);
-
-bool
-check_bool(struct client *client, bool *value_r, const char *s);
-
-bool
-check_float(struct client *client, float *value_r, const char *s);
-
-#endif
diff --git a/src/protocol/result.c b/src/protocol/result.c
deleted file mode 100644
index 30cd0a266..000000000
--- a/src/protocol/result.c
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * 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 "result.h"
-#include "client.h"
-
-#include <assert.h>
-
-const char *current_command;
-int command_list_num;
-
-void
-command_success(struct client *client)
-{
- client_puts(client, "OK\n");
-}
-
-void
-command_error_v(struct client *client, enum ack error,
- const char *fmt, va_list args)
-{
- assert(client != NULL);
- assert(current_command != NULL);
-
- client_printf(client, "ACK [%i@%i] {%s} ",
- (int)error, command_list_num, current_command);
- client_vprintf(client, fmt, args);
- client_puts(client, "\n");
-
- current_command = NULL;
-}
-
-void
-command_error(struct client *client, enum ack error, const char *fmt, ...)
-{
- va_list args;
- va_start(args, fmt);
- command_error_v(client, error, fmt, args);
- va_end(args);
-}
diff --git a/src/protocol/result.h b/src/protocol/result.h
deleted file mode 100644
index 8b9e44bfd..000000000
--- a/src/protocol/result.h
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef MPD_PROTOCOL_RESULT_H
-#define MPD_PROTOCOL_RESULT_H
-
-#include "check.h"
-#include "ack.h"
-
-#include <glib.h>
-
-struct client;
-
-extern const char *current_command;
-extern int command_list_num;
-
-void
-command_success(struct client *client);
-
-void
-command_error_v(struct client *client, enum ack error,
- const char *fmt, va_list args);
-
-G_GNUC_PRINTF(3, 4)
-void
-command_error(struct client *client, enum ack error, const char *fmt, ...);
-
-#endif
diff --git a/src/queue.c b/src/queue.c
deleted file mode 100644
index 4fe564a35..000000000
--- a/src/queue.c
+++ /dev/null
@@ -1,603 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "queue.h"
-#include "song.h"
-
-#include <stdlib.h>
-
-/**
- * Generate a non-existing id number.
- */
-static unsigned
-queue_generate_id(const struct queue *queue)
-{
- static unsigned cur = (unsigned)-1;
-
- do {
- cur++;
-
- if (cur >= queue->max_length * QUEUE_HASH_MULT)
- cur = 0;
- } while (queue->id_to_position[cur] != -1);
-
- return cur;
-}
-
-int
-queue_next_order(const struct queue *queue, unsigned order)
-{
- assert(order < queue->length);
-
- if (queue->single && queue->repeat && !queue->consume)
- return order;
- else if (order + 1 < queue->length)
- return order + 1;
- else if (queue->repeat && (order > 0 || !queue->consume))
- /* restart at first song */
- return 0;
- else
- /* end of queue */
- return -1;
-}
-
-void
-queue_increment_version(struct queue *queue)
-{
- static unsigned long max = ((uint32_t) 1 << 31) - 1;
-
- queue->version++;
-
- if (queue->version >= max) {
- for (unsigned i = 0; i < queue->length; i++)
- queue->items[i].version = 0;
-
- queue->version = 1;
- }
-}
-
-void
-queue_modify(struct queue *queue, unsigned order)
-{
- unsigned position;
-
- assert(order < queue->length);
-
- position = queue->order[order];
- queue->items[position].version = queue->version;
-
- queue_increment_version(queue);
-}
-
-void
-queue_modify_all(struct queue *queue)
-{
- for (unsigned i = 0; i < queue->length; i++)
- queue->items[i].version = queue->version;
-
- queue_increment_version(queue);
-}
-
-unsigned
-queue_append(struct queue *queue, struct song *song, uint8_t priority)
-{
- unsigned id = queue_generate_id(queue);
-
- assert(!queue_is_full(queue));
-
- queue->items[queue->length] = (struct queue_item){
- .song = song,
- .id = id,
- .version = queue->version,
- .priority = priority,
- };
-
- queue->order[queue->length] = queue->length;
- queue->id_to_position[id] = queue->length;
-
- ++queue->length;
-
- return id;
-}
-
-void
-queue_swap(struct queue *queue, unsigned position1, unsigned position2)
-{
- struct queue_item tmp;
- unsigned id1 = queue->items[position1].id;
- unsigned id2 = queue->items[position2].id;
-
- tmp = queue->items[position1];
- queue->items[position1] = queue->items[position2];
- queue->items[position2] = tmp;
-
- queue->items[position1].version = queue->version;
- queue->items[position2].version = queue->version;
-
- queue->id_to_position[id1] = position2;
- queue->id_to_position[id2] = position1;
-}
-
-static void
-queue_move_song_to(struct queue *queue, unsigned from, unsigned to)
-{
- unsigned from_id = queue->items[from].id;
-
- queue->items[to] = queue->items[from];
- queue->items[to].version = queue->version;
- queue->id_to_position[from_id] = to;
-}
-
-void
-queue_move(struct queue *queue, unsigned from, unsigned to)
-{
- struct queue_item item = queue->items[from];
-
- /* move songs to one less in from->to */
-
- for (unsigned i = from; i < to; i++)
- queue_move_song_to(queue, i + 1, i);
-
- /* move songs to one more in to->from */
-
- for (unsigned i = from; i > to; i--)
- queue_move_song_to(queue, i - 1, i);
-
- /* put song at _to_ */
-
- queue->id_to_position[item.id] = to;
- queue->items[to] = item;
- queue->items[to].version = queue->version;
-
- /* now deal with order */
-
- if (queue->random) {
- for (unsigned i = 0; i < queue->length; i++) {
- if (queue->order[i] > from && queue->order[i] <= to)
- queue->order[i]--;
- else if (queue->order[i] < from &&
- queue->order[i] >= to)
- queue->order[i]++;
- else if (from == queue->order[i])
- queue->order[i] = to;
- }
- }
-}
-
-void
-queue_move_range(struct queue *queue, unsigned start, unsigned end, unsigned to)
-{
- struct queue_item items[end - start];
- // Copy the original block [start,end-1]
- for (unsigned i = start; i < end; i++)
- items[i - start] = queue->items[i];
-
- // If to > start, we need to move to-start items to start, starting from end
- for (unsigned i = end; i < end + to - start; i++)
- queue_move_song_to(queue, i, start + i - end);
-
- // If to < start, we need to move start-to items to newend (= end + to - start), starting from to
- // This is the same as moving items from start-1 to to (decreasing), with start-1 going to end-1
- // We have to iterate in this order to avoid writing over something we haven't yet moved
- for (unsigned i = start - 1; i >= to && i != G_MAXUINT; i--)
- queue_move_song_to(queue, i, i + end - start);
-
- // Copy the original block back in, starting at to.
- for (unsigned i = start; i< end; i++)
- {
- queue->id_to_position[items[i-start].id] = to + i - start;
- queue->items[to + i - start] = items[i-start];
- queue->items[to + i - start].version = queue->version;
- }
-
- if (queue->random) {
- // Update the positions in the queue.
- // Note that the ranges for these cases are the same as the ranges of
- // the loops above.
- for (unsigned i = 0; i < queue->length; i++) {
- if (queue->order[i] >= end && queue->order[i] < to + end - start)
- queue->order[i] -= end - start;
- else if (queue->order[i] < start &&
- queue->order[i] >= to)
- queue->order[i] += end - start;
- else if (start <= queue->order[i] && queue->order[i] < end)
- queue->order[i] += to - start;
- }
- }
-}
-
-/**
- * Moves a song to a new position in the "order" list.
- */
-static void
-queue_move_order(struct queue *queue, unsigned from_order, unsigned to_order)
-{
- assert(queue != NULL);
- assert(from_order < queue->length);
- assert(to_order <= queue->length);
-
- const unsigned from_position =
- queue_order_to_position(queue, from_order);
-
- if (from_order < to_order) {
- for (unsigned i = from_order; i < to_order; ++i)
- queue->order[i] = queue->order[i + 1];
- } else {
- for (unsigned i = from_order; i > to_order; --i)
- queue->order[i] = queue->order[i - 1];
- }
-
- queue->order[to_order] = from_position;
-}
-
-void
-queue_delete(struct queue *queue, unsigned position)
-{
- struct song *song;
- unsigned id, order;
-
- assert(position < queue->length);
-
- song = queue_get(queue, position);
- if (!song_in_database(song))
- song_free(song);
-
- id = queue_position_to_id(queue, position);
- order = queue_position_to_order(queue, position);
-
- --queue->length;
-
- /* release the song id */
-
- queue->id_to_position[id] = -1;
-
- /* delete song from songs array */
-
- for (unsigned i = position; i < queue->length; i++)
- queue_move_song_to(queue, i + 1, i);
-
- /* delete the entry from the order array */
-
- for (unsigned i = order; i < queue->length; i++)
- queue->order[i] = queue->order[i + 1];
-
- /* readjust values in the order array */
-
- for (unsigned i = 0; i < queue->length; i++)
- if (queue->order[i] > position)
- --queue->order[i];
-}
-
-void
-queue_clear(struct queue *queue)
-{
- for (unsigned i = 0; i < queue->length; i++) {
- struct queue_item *item = &queue->items[i];
-
- if (!song_in_database(item->song))
- song_free(item->song);
-
- queue->id_to_position[item->id] = -1;
- }
-
- queue->length = 0;
-}
-
-void
-queue_init(struct queue *queue, unsigned max_length)
-{
- queue->max_length = max_length;
- queue->length = 0;
- queue->version = 1;
- queue->repeat = false;
- queue->random = false;
- queue->single = false;
- queue->consume = false;
-
- queue->items = g_new(struct queue_item, max_length);
- queue->order = g_malloc(sizeof(queue->order[0]) *
- max_length);
- queue->id_to_position = g_malloc(sizeof(queue->id_to_position[0]) *
- max_length * QUEUE_HASH_MULT);
-
- for (unsigned i = 0; i < max_length * QUEUE_HASH_MULT; ++i)
- queue->id_to_position[i] = -1;
-
- queue->rand = g_rand_new();
-}
-
-void
-queue_finish(struct queue *queue)
-{
- queue_clear(queue);
-
- g_free(queue->items);
- g_free(queue->order);
- g_free(queue->id_to_position);
-
- g_rand_free(queue->rand);
-}
-
-static const struct queue_item *
-queue_get_order_item_const(const struct queue *queue, unsigned order)
-{
- assert(queue != NULL);
- assert(order < queue->length);
-
- return &queue->items[queue->order[order]];
-}
-
-static uint8_t
-queue_get_order_priority(const struct queue *queue, unsigned order)
-{
- return queue_get_order_item_const(queue, order)->priority;
-}
-
-static gint
-queue_item_compare_order_priority(gconstpointer av, gconstpointer bv,
- gpointer user_data)
-{
- const struct queue *queue = user_data;
- const unsigned *const ap = av;
- const unsigned *const bp = bv;
- assert(ap >= queue->order && ap < queue->order + queue->length);
- assert(bp >= queue->order && bp < queue->order + queue->length);
- uint8_t a = queue->items[*ap].priority;
- uint8_t b = queue->items[*bp].priority;
-
- if (G_LIKELY(a == b))
- return 0;
- else if (a > b)
- return -1;
- else
- return 1;
-}
-
-static void
-queue_sort_order_by_priority(struct queue *queue, unsigned start, unsigned end)
-{
- assert(queue != NULL);
- assert(queue->random);
- assert(start <= end);
- assert(end <= queue->length);
-
- g_qsort_with_data(&queue->order[start], end - start,
- sizeof(queue->order[0]),
- queue_item_compare_order_priority,
- queue);
-}
-
-/**
- * Shuffle the order of items in the specified range, ignoring their
- * priorities.
- */
-static void
-queue_shuffle_order_range(struct queue *queue, unsigned start, unsigned end)
-{
- assert(queue != NULL);
- assert(queue->random);
- assert(start <= end);
- assert(end <= queue->length);
-
- for (unsigned i = start; i < end; ++i)
- queue_swap_order(queue, i,
- g_rand_int_range(queue->rand, i, end));
-}
-
-/**
- * Sort the "order" of items by priority, and then shuffle each
- * priority group.
- */
-void
-queue_shuffle_order_range_with_priority(struct queue *queue,
- unsigned start, unsigned end)
-{
- assert(queue != NULL);
- assert(queue->random);
- assert(start <= end);
- assert(end <= queue->length);
-
- if (start == end)
- return;
-
- /* first group the range by priority */
- queue_sort_order_by_priority(queue, start, end);
-
- /* now shuffle each priority group */
- unsigned group_start = start;
- uint8_t group_priority = queue_get_order_priority(queue, start);
-
- for (unsigned i = start + 1; i < end; ++i) {
- uint8_t priority = queue_get_order_priority(queue, i);
- assert(priority <= group_priority);
-
- if (priority != group_priority) {
- /* start of a new group - shuffle the one that
- has just ended */
- queue_shuffle_order_range(queue, group_start, i);
- group_start = i;
- group_priority = priority;
- }
- }
-
- /* shuffle the last group */
- queue_shuffle_order_range(queue, group_start, end);
-}
-
-void
-queue_shuffle_order(struct queue *queue)
-{
- queue_shuffle_order_range_with_priority(queue, 0, queue->length);
-}
-
-static void
-queue_shuffle_order_first(struct queue *queue, unsigned start, unsigned end)
-{
- queue_swap_order(queue, start,
- g_rand_int_range(queue->rand, start, end));
-}
-
-void
-queue_shuffle_order_last(struct queue *queue, unsigned start, unsigned end)
-{
- queue_swap_order(queue, end - 1,
- g_rand_int_range(queue->rand, start, end));
-}
-
-void
-queue_shuffle_range(struct queue *queue, unsigned start, unsigned end)
-{
- assert(start <= end);
- assert(end <= queue->length);
-
- for (unsigned i = start; i < end; i++) {
- unsigned ri = g_rand_int_range(queue->rand, i, end);
- queue_swap(queue, i, ri);
- }
-}
-
-/**
- * Find the first item that has this specified priority or higher.
- */
-G_GNUC_PURE
-static unsigned
-queue_find_priority_order(const struct queue *queue, unsigned start_order,
- uint8_t priority, unsigned exclude_order)
-{
- assert(queue != NULL);
- assert(queue->random);
- assert(start_order <= queue->length);
-
- for (unsigned order = start_order; order < queue->length; ++order) {
- const unsigned position = queue_order_to_position(queue, order);
- const struct queue_item *item = &queue->items[position];
- if (item->priority <= priority && order != exclude_order)
- return order;
- }
-
- return queue->length;
-}
-
-G_GNUC_PURE
-static unsigned
-queue_count_same_priority(const struct queue *queue, unsigned start_order,
- uint8_t priority)
-{
- assert(queue != NULL);
- assert(queue->random);
- assert(start_order <= queue->length);
-
- for (unsigned order = start_order; order < queue->length; ++order) {
- const unsigned position = queue_order_to_position(queue, order);
- const struct queue_item *item = &queue->items[position];
- if (item->priority != priority)
- return order - start_order;
- }
-
- return queue->length - start_order;
-}
-
-bool
-queue_set_priority(struct queue *queue, unsigned position, uint8_t priority,
- int after_order)
-{
- assert(queue != NULL);
- assert(position < queue->length);
-
- struct queue_item *item = &queue->items[position];
- uint8_t old_priority = item->priority;
- if (old_priority == priority)
- return false;
-
- item->version = queue->version;
- item->priority = priority;
-
- if (!queue->random)
- /* don't reorder if not in random mode */
- return true;
-
- unsigned order = queue_position_to_order(queue, position);
- if (after_order >= 0) {
- if (order == (unsigned)after_order)
- /* don't reorder the current song */
- return true;
-
- if (order < (unsigned)after_order) {
- /* the specified song has been played already
- - enqueue it only if its priority has just
- become bigger than the current one's */
-
- const unsigned after_position =
- queue_order_to_position(queue, after_order);
- const struct queue_item *after_item =
- &queue->items[after_position];
- if (old_priority > after_item->priority ||
- priority <= after_item->priority)
- /* priority hasn't become bigger */
- return true;
- }
- }
-
- /* move the item to the beginning of the priority group (or
- create a new priority group) */
-
- const unsigned before_order =
- queue_find_priority_order(queue, after_order + 1, priority,
- order);
- const unsigned new_order = before_order > order
- ? before_order - 1
- : before_order;
- queue_move_order(queue, order, new_order);
-
- /* shuffle the song within that priority group */
-
- const unsigned priority_count =
- queue_count_same_priority(queue, new_order, priority);
- assert(priority_count >= 1);
- queue_shuffle_order_first(queue, new_order,
- new_order + priority_count);
-
- return true;
-}
-
-bool
-queue_set_priority_range(struct queue *queue,
- unsigned start_position, unsigned end_position,
- uint8_t priority, int after_order)
-{
- assert(queue != NULL);
- assert(start_position <= end_position);
- assert(end_position <= queue->length);
-
- bool modified = false;
- int after_position = after_order >= 0
- ? (int)queue_order_to_position(queue, after_order)
- : -1;
- for (unsigned i = start_position; i < end_position; ++i) {
- after_order = after_position >= 0
- ? (int)queue_position_to_order(queue, after_position)
- : -1;
-
- modified |= queue_set_priority(queue, i, priority,
- after_order);
- }
-
- return modified;
-}
diff --git a/src/queue.h b/src/queue.h
deleted file mode 100644
index e4bfcdffa..000000000
--- a/src/queue.h
+++ /dev/null
@@ -1,379 +0,0 @@
-/*
- * 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 QUEUE_H
-#define QUEUE_H
-
-#include <glib.h>
-
-#include <assert.h>
-#include <stdbool.h>
-#include <stdint.h>
-
-enum {
- /**
- * reserve max_length * QUEUE_HASH_MULT elements in the id
- * number space
- */
- QUEUE_HASH_MULT = 4,
-};
-
-/**
- * One element of the queue: basically a song plus some queue specific
- * information attached.
- */
-struct queue_item {
- struct song *song;
-
- /** the unique id of this item in the queue */
- unsigned id;
-
- /** when was this item last changed? */
- uint32_t version;
-
- /**
- * The priority of this item, between 0 and 255. High
- * priority value means that this song gets played first in
- * "random" mode.
- */
- uint8_t priority;
-};
-
-/**
- * A queue of songs. This is the backend of the playlist: it contains
- * an ordered list of songs.
- *
- * Songs can be addressed in three possible ways:
- *
- * - the position in the queue
- * - the unique id (which stays the same, regardless of moves)
- * - the order number (which only differs from "position" in random mode)
- */
-struct queue {
- /** configured maximum length of the queue */
- unsigned max_length;
-
- /** number of songs in the queue */
- unsigned length;
-
- /** the current version number */
- uint32_t version;
-
- /** all songs in "position" order */
- struct queue_item *items;
-
- /** map order numbers to positions */
- unsigned *order;
-
- /** map song ids to positions */
- int *id_to_position;
-
- /** repeat playback when the end of the queue has been
- reached? */
- bool repeat;
-
- /** play only current song. */
- bool single;
-
- /** remove each played files. */
- bool consume;
-
- /** play back songs in random order? */
- bool random;
-
- /** random number generator for shuffle and random mode */
- GRand *rand;
-};
-
-static inline unsigned
-queue_length(const struct queue *queue)
-{
- assert(queue->length <= queue->max_length);
-
- return queue->length;
-}
-
-/**
- * Determine if the queue is empty, i.e. there are no songs.
- */
-static inline bool
-queue_is_empty(const struct queue *queue)
-{
- return queue->length == 0;
-}
-
-/**
- * Determine if the maximum number of songs has been reached.
- */
-static inline bool
-queue_is_full(const struct queue *queue)
-{
- assert(queue->length <= queue->max_length);
-
- return queue->length >= queue->max_length;
-}
-
-/**
- * Is that a valid position number?
- */
-static inline bool
-queue_valid_position(const struct queue *queue, unsigned position)
-{
- return position < queue->length;
-}
-
-/**
- * Is that a valid order number?
- */
-static inline bool
-queue_valid_order(const struct queue *queue, unsigned order)
-{
- return order < queue->length;
-}
-
-static inline int
-queue_id_to_position(const struct queue *queue, unsigned id)
-{
- if (id >= queue->max_length * QUEUE_HASH_MULT)
- return -1;
-
- assert(queue->id_to_position[id] >= -1);
- assert(queue->id_to_position[id] < (int)queue->length);
-
- return queue->id_to_position[id];
-}
-
-static inline int
-queue_position_to_id(const struct queue *queue, unsigned position)
-{
- assert(position < queue->length);
-
- return queue->items[position].id;
-}
-
-static inline unsigned
-queue_order_to_position(const struct queue *queue, unsigned order)
-{
- assert(order < queue->length);
-
- return queue->order[order];
-}
-
-static inline unsigned
-queue_position_to_order(const struct queue *queue, unsigned position)
-{
- assert(position < queue->length);
-
- for (unsigned i = 0;; ++i) {
- assert(i < queue->length);
-
- if (queue->order[i] == position)
- return i;
- }
-}
-
-G_GNUC_PURE
-static inline uint8_t
-queue_get_priority_at_position(const struct queue *queue, unsigned position)
-{
- assert(position < queue->length);
-
- return queue->items[position].priority;
-}
-
-/**
- * Returns the song at the specified position.
- */
-static inline struct song *
-queue_get(const struct queue *queue, unsigned position)
-{
- assert(position < queue->length);
-
- return queue->items[position].song;
-}
-
-/**
- * Returns the song at the specified order number.
- */
-static inline struct song *
-queue_get_order(const struct queue *queue, unsigned order)
-{
- return queue_get(queue, queue_order_to_position(queue, order));
-}
-
-/**
- * Is the song at the specified position newer than the specified
- * version?
- */
-static inline bool
-queue_song_newer(const struct queue *queue, unsigned position,
- uint32_t version)
-{
- assert(position < queue->length);
-
- return version > queue->version ||
- queue->items[position].version >= version ||
- queue->items[position].version == 0;
-}
-
-/**
- * Initialize a queue object.
- */
-void
-queue_init(struct queue *queue, unsigned max_length);
-
-/**
- * Deinitializes a queue object. It does not free the queue pointer
- * itself.
- */
-void
-queue_finish(struct queue *queue);
-
-/**
- * Returns the order number following the specified one. This takes
- * end of queue and "repeat" mode into account.
- *
- * @return the next order number, or -1 to stop playback
- */
-int
-queue_next_order(const struct queue *queue, unsigned order);
-
-/**
- * Increments the queue's version number. This handles integer
- * overflow well.
- */
-void
-queue_increment_version(struct queue *queue);
-
-/**
- * Marks the specified song as "modified" and increments the version
- * number.
- */
-void
-queue_modify(struct queue *queue, unsigned order);
-
-/**
- * Marks all songs as "modified" and increments the version number.
- */
-void
-queue_modify_all(struct queue *queue);
-
-/**
- * Appends a song to the queue and returns its position. Prior to
- * that, the caller must check if the queue is already full.
- *
- * If a song is not in the database (determined by
- * song_in_database()), it is freed when removed from the queue.
- *
- * @param priority the priority of this new queue item
- */
-unsigned
-queue_append(struct queue *queue, struct song *song, uint8_t priority);
-
-/**
- * Swaps two songs, addressed by their position.
- */
-void
-queue_swap(struct queue *queue, unsigned position1, unsigned position2);
-
-/**
- * Swaps two songs, addressed by their order number.
- */
-static inline void
-queue_swap_order(struct queue *queue, unsigned order1, unsigned order2)
-{
- unsigned tmp = queue->order[order1];
- queue->order[order1] = queue->order[order2];
- queue->order[order2] = tmp;
-}
-
-/**
- * Moves a song to a new position.
- */
-void
-queue_move(struct queue *queue, unsigned from, unsigned to);
-
-/**
- * Moves a range of songs to a new position.
- */
-void
-queue_move_range(struct queue *queue, unsigned start, unsigned end, unsigned to);
-
-/**
- * Removes a song from the playlist.
- */
-void
-queue_delete(struct queue *queue, unsigned position);
-
-/**
- * Removes all songs from the playlist.
- */
-void
-queue_clear(struct queue *queue);
-
-/**
- * Initializes the "order" array, and restores "normal" order.
- */
-static inline void
-queue_restore_order(struct queue *queue)
-{
- for (unsigned i = 0; i < queue->length; ++i)
- queue->order[i] = i;
-}
-
-/**
- * Shuffle the order of items in the specified range, taking their
- * priorities into account.
- */
-void
-queue_shuffle_order_range_with_priority(struct queue *queue,
- unsigned start, unsigned end);
-
-/**
- * Shuffles the virtual order of songs, but does not move them
- * physically. This is used in random mode.
- */
-void
-queue_shuffle_order(struct queue *queue);
-
-/**
- * Shuffles the virtual order of the last song in the specified
- * (order) range. This is used in random mode after a song has been
- * appended by queue_append().
- */
-void
-queue_shuffle_order_last(struct queue *queue, unsigned start, unsigned end);
-
-/**
- * Shuffles a (position) range in the queue. The songs are physically
- * shuffled, not by using the "order" mapping.
- */
-void
-queue_shuffle_range(struct queue *queue, unsigned start, unsigned end);
-
-bool
-queue_set_priority(struct queue *queue, unsigned position,
- uint8_t priority, int after_order);
-
-bool
-queue_set_priority_range(struct queue *queue,
- unsigned start_position, unsigned end_position,
- uint8_t priority, int after_order);
-
-#endif
diff --git a/src/queue_print.c b/src/queue_print.c
deleted file mode 100644
index d149e8b6f..000000000
--- a/src/queue_print.c
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "queue_print.h"
-#include "queue.h"
-#include "song.h"
-#include "song_print.h"
-#include "locate.h"
-#include "client.h"
-#include "mapper.h"
-
-/**
- * Send detailed information about a range of songs in the queue to a
- * client.
- *
- * @param client the client which has requested information
- * @param start the index of the first song (including)
- * @param end the index of the last song (excluding)
- */
-static void
-queue_print_song_info(struct client *client, const struct queue *queue,
- unsigned position)
-{
- song_print_info(client, queue_get(queue, position));
- client_printf(client, "Pos: %u\nId: %u\n",
- position, queue_position_to_id(queue, position));
-
- uint8_t priority = queue_get_priority_at_position(queue, position);
- if (priority != 0)
- client_printf(client, "Prio: %u\n", priority);
-}
-
-void
-queue_print_info(struct client *client, const struct queue *queue,
- unsigned start, unsigned end)
-{
- assert(start <= end);
- assert(end <= queue_length(queue));
-
- for (unsigned i = start; i < end; ++i)
- queue_print_song_info(client, queue, i);
-}
-
-void
-queue_print_uris(struct client *client, const struct queue *queue,
- unsigned start, unsigned end)
-{
- assert(start <= end);
- assert(end <= queue_length(queue));
-
- for (unsigned i = start; i < end; ++i) {
- client_printf(client, "%i:", i);
- song_print_uri(client, queue_get(queue, i));
- }
-}
-
-void
-queue_print_changes_info(struct client *client, const struct queue *queue,
- uint32_t version)
-{
- for (unsigned i = 0; i < queue_length(queue); i++) {
- if (queue_song_newer(queue, i, version))
- queue_print_song_info(client, queue, i);
- }
-}
-
-void
-queue_print_changes_position(struct client *client, const struct queue *queue,
- uint32_t version)
-{
- for (unsigned i = 0; i < queue_length(queue); i++)
- if (queue_song_newer(queue, i, version))
- client_printf(client, "cpos: %i\nId: %i\n",
- i, queue_position_to_id(queue, i));
-}
-
-void
-queue_search(struct client *client, const struct queue *queue,
- const struct locate_item_list *criteria)
-{
- unsigned i;
- struct locate_item_list *new_list =
- locate_item_list_casefold(criteria);
-
- for (i = 0; i < queue_length(queue); i++) {
- const struct song *song = queue_get(queue, i);
-
- if (locate_song_search(song, new_list))
- queue_print_song_info(client, queue, i);
- }
-
- locate_item_list_free(new_list);
-}
-
-void
-queue_find(struct client *client, const struct queue *queue,
- const struct locate_item_list *criteria)
-{
- for (unsigned i = 0; i < queue_length(queue); i++) {
- const struct song *song = queue_get(queue, i);
-
- if (locate_song_match(song, criteria))
- queue_print_song_info(client, queue, i);
- }
-}
diff --git a/src/queue_print.h b/src/queue_print.h
deleted file mode 100644
index 371e20416..000000000
--- a/src/queue_print.h
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * This library sends information about songs in the queue to the
- * client.
- */
-
-#ifndef QUEUE_PRINT_H
-#define QUEUE_PRINT_H
-
-#include <stdint.h>
-
-struct client;
-struct queue;
-struct locate_item_list;
-
-void
-queue_print_info(struct client *client, const struct queue *queue,
- unsigned start, unsigned end);
-
-void
-queue_print_uris(struct client *client, const struct queue *queue,
- unsigned start, unsigned end);
-
-void
-queue_print_changes_info(struct client *client, const struct queue *queue,
- uint32_t version);
-
-void
-queue_print_changes_position(struct client *client, const struct queue *queue,
- uint32_t version);
-
-void
-queue_search(struct client *client, const struct queue *queue,
- const struct locate_item_list *criteria);
-
-void
-queue_find(struct client *client, const struct queue *queue,
- const struct locate_item_list *criteria);
-
-#endif
diff --git a/src/queue_save.c b/src/queue_save.c
deleted file mode 100644
index 16852d3c1..000000000
--- a/src/queue_save.c
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "queue_save.h"
-#include "queue.h"
-#include "song.h"
-#include "uri.h"
-#include "database.h"
-#include "song_save.h"
-#include "text_file.h"
-
-#include <stdlib.h>
-
-#define PRIO_LABEL "Prio: "
-
-static void
-queue_save_database_song(FILE *fp, int idx, const struct song *song)
-{
- char *uri = song_get_uri(song);
-
- fprintf(fp, "%i:%s\n", idx, uri);
- g_free(uri);
-}
-
-static void
-queue_save_full_song(FILE *fp, const struct song *song)
-{
- song_save(fp, song);
-}
-
-static void
-queue_save_song(FILE *fp, int idx, const struct song *song)
-{
- if (song_in_database(song))
- queue_save_database_song(fp, idx, song);
- else
- queue_save_full_song(fp, song);
-}
-
-void
-queue_save(FILE *fp, const struct queue *queue)
-{
- for (unsigned i = 0; i < queue_length(queue); i++) {
- uint8_t prio = queue_get_priority_at_position(queue, i);
- if (prio != 0)
- fprintf(fp, PRIO_LABEL "%u\n", prio);
-
- queue_save_song(fp, i, queue_get(queue, i));
- }
-}
-
-static struct song *
-get_song(const char *uri)
-{
- return uri_has_scheme(uri)
- ? song_remote_new(uri)
- : db_get_song(uri);
-}
-
-void
-queue_load_song(FILE *fp, GString *buffer, const char *line,
- struct queue *queue)
-{
- struct song *song;
-
- if (queue_is_full(queue))
- return;
-
- uint8_t priority = 0;
- if (g_str_has_prefix(line, PRIO_LABEL)) {
- priority = strtoul(line + sizeof(PRIO_LABEL) - 1, NULL, 10);
-
- line = read_text_line(fp, buffer);
- if (line == NULL)
- return;
- }
-
- if (g_str_has_prefix(line, SONG_BEGIN)) {
- const char *uri = line + sizeof(SONG_BEGIN) - 1;
- if (!uri_has_scheme(uri) && !g_path_is_absolute(uri))
- return;
-
- GError *error = NULL;
- song = song_load(fp, NULL, uri, buffer, &error);
- if (song == NULL) {
- g_warning("%s", error->message);
- g_error_free(error);
- return;
- }
- } else {
- char *endptr;
- long ret = strtol(line, &endptr, 10);
- if (ret < 0 || *endptr != ':' || endptr[1] == 0) {
- g_warning("Malformed playlist line in state file");
- return;
- }
-
- line = endptr + 1;
-
- song = get_song(line);
- if (song == NULL)
- return;
- }
-
- queue_append(queue, song, priority);
-}
diff --git a/src/queue_save.h b/src/queue_save.h
deleted file mode 100644
index 5526d615d..000000000
--- a/src/queue_save.h
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * This library saves the queue into the state file, and also loads it
- * back into memory.
- */
-
-#ifndef QUEUE_SAVE_H
-#define QUEUE_SAVE_H
-
-#include <glib.h>
-#include <stdio.h>
-
-struct queue;
-
-void
-queue_save(FILE *fp, const struct queue *queue);
-
-/**
- * Loads one song from the state file and appends it to the queue.
- */
-void
-queue_load_song(FILE *fp, GString *buffer, const char *line,
- struct queue *queue);
-
-#endif
diff --git a/src/refcount.h b/src/refcount.h
deleted file mode 100644
index a882d76b0..000000000
--- a/src/refcount.h
+++ /dev/null
@@ -1,67 +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
- *
- * A very simple reference counting library.
- */
-
-#ifndef MPD_REFCOUNT_H
-#define MPD_REFCOUNT_H
-
-#include <glib.h>
-#include <stdbool.h>
-
-struct refcount {
- gint n;
-};
-
-static inline void
-refcount_init(struct refcount *r)
-{
- r->n = 1;
-}
-
-static inline void
-refcount_inc(struct refcount *r)
-{
- g_atomic_int_inc(&r->n);
-}
-
-/**
- * @return true if the number of references has been dropped to 0
- */
-static inline bool
-refcount_dec(struct refcount *r)
-{
- return g_atomic_int_dec_and_test(&r->n);
-}
-
-#endif
diff --git a/src/replay_gain_ape.c b/src/replay_gain_ape.c
deleted file mode 100644
index 0b59e3c02..000000000
--- a/src/replay_gain_ape.c
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "replay_gain_ape.h"
-#include "replay_gain_info.h"
-#include "ape.h"
-
-#include <glib.h>
-
-#include <string.h>
-#include <stdlib.h>
-
-struct rg_ape_ctx {
- struct replay_gain_info *info;
- bool found;
-};
-
-static bool
-replay_gain_ape_callback(unsigned long flags, const char *key,
- const char *_value, size_t value_length, void *_ctx)
-{
- struct rg_ape_ctx *ctx = _ctx;
-
- /* we only care about utf-8 text tags */
- if ((flags & (0x3 << 1)) != 0)
- return true;
-
- char value[16];
- if (value_length >= sizeof(value))
- return true;
- memcpy(value, _value, value_length);
- value[value_length] = 0;
-
- if (g_ascii_strcasecmp(key, "replaygain_track_gain") == 0) {
- ctx->info->tuples[REPLAY_GAIN_TRACK].gain = atof(value);
- ctx->found = true;
- } else if (g_ascii_strcasecmp(key, "replaygain_album_gain") == 0) {
- ctx->info->tuples[REPLAY_GAIN_ALBUM].gain = atof(value);
- ctx->found = true;
- } else if (g_ascii_strcasecmp(key, "replaygain_track_peak") == 0) {
- ctx->info->tuples[REPLAY_GAIN_TRACK].peak = atof(value);
- ctx->found = true;
- } else if (g_ascii_strcasecmp(key, "replaygain_album_peak") == 0) {
- ctx->info->tuples[REPLAY_GAIN_ALBUM].peak = atof(value);
- ctx->found = true;
- }
-
- return true;
-}
-
-bool
-replay_gain_ape_read(const char *path_fs, struct replay_gain_info *info)
-{
- struct rg_ape_ctx ctx = {
- .info = info,
- .found = false,
- };
-
- return tag_ape_scan(path_fs, replay_gain_ape_callback, &ctx) &&
- ctx.found;
-}
diff --git a/src/replay_gain_ape.h b/src/replay_gain_ape.h
deleted file mode 100644
index 35760a0aa..000000000
--- a/src/replay_gain_ape.h
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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_REPLAY_GAIN_APE_H
-#define MPD_REPLAY_GAIN_APE_H
-
-#include "check.h"
-
-#include <stdbool.h>
-
-struct replay_gain_info;
-
-bool
-replay_gain_ape_read(const char *path_fs, struct replay_gain_info *info);
-
-#endif
diff --git a/src/replay_gain_config.c b/src/replay_gain_config.c
deleted file mode 100644
index 2181387b7..000000000
--- a/src/replay_gain_config.c
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "replay_gain_config.h"
-#include "playlist.h"
-#include "conf.h"
-#include "idle.h"
-#include "mpd_error.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <stdlib.h>
-#include <string.h>
-#include <math.h>
-
-static const char *const replay_gain_mode_names[] = {
- [REPLAY_GAIN_ALBUM] = "album",
- [REPLAY_GAIN_TRACK] = "track",
-};
-
-enum replay_gain_mode replay_gain_mode = REPLAY_GAIN_OFF;
-
-const bool DEFAULT_REPLAYGAIN_LIMIT = true;
-
-float replay_gain_preamp = 1.0;
-float replay_gain_missing_preamp = 1.0;
-bool replay_gain_limit;
-
-const char *
-replay_gain_get_mode_string(void)
-{
- switch (replay_gain_mode) {
- case REPLAY_GAIN_AUTO:
- return "auto";
-
- case REPLAY_GAIN_OFF:
- return "off";
-
- case REPLAY_GAIN_TRACK:
- return "track";
-
- case REPLAY_GAIN_ALBUM:
- return "album";
- }
-
- /* unreachable */
- assert(false);
- return "off";
-}
-
-bool
-replay_gain_set_mode_string(const char *p)
-{
- assert(p != NULL);
-
- if (strcmp(p, "off") == 0)
- replay_gain_mode = REPLAY_GAIN_OFF;
- else if (strcmp(p, "track") == 0)
- replay_gain_mode = REPLAY_GAIN_TRACK;
- else if (strcmp(p, "album") == 0)
- replay_gain_mode = REPLAY_GAIN_ALBUM;
- else if (strcmp(p, "auto") == 0)
- replay_gain_mode = REPLAY_GAIN_AUTO;
- else
- return false;
-
- idle_add(IDLE_OPTIONS);
-
- return true;
-}
-
-void replay_gain_global_init(void)
-{
- const struct config_param *param = config_get_param(CONF_REPLAYGAIN);
-
- if (param != NULL && !replay_gain_set_mode_string(param->value)) {
- MPD_ERROR("replaygain value \"%s\" at line %i is invalid\n",
- param->value, param->line);
- }
-
- param = config_get_param(CONF_REPLAYGAIN_PREAMP);
-
- if (param) {
- char *test;
- float f = strtod(param->value, &test);
-
- if (*test != '\0') {
- MPD_ERROR("Replaygain preamp \"%s\" is not a number at "
- "line %i\n", param->value, param->line);
- }
-
- if (f < -15 || f > 15) {
- MPD_ERROR("Replaygain preamp \"%s\" is not between -15 and"
- "15 at line %i\n", param->value, param->line);
- }
-
- replay_gain_preamp = pow(10, f / 20.0);
- }
-
- param = config_get_param(CONF_REPLAYGAIN_MISSING_PREAMP);
-
- if (param) {
- char *test;
- float f = strtod(param->value, &test);
-
- if (*test != '\0') {
- MPD_ERROR("Replaygain missing preamp \"%s\" is not a number at "
- "line %i\n", param->value, param->line);
- }
-
- if (f < -15 || f > 15) {
- MPD_ERROR("Replaygain missing preamp \"%s\" is not between -15 and"
- "15 at line %i\n", param->value, param->line);
- }
-
- replay_gain_missing_preamp = pow(10, f / 20.0);
- }
-
- replay_gain_limit = config_get_bool(CONF_REPLAYGAIN_LIMIT, DEFAULT_REPLAYGAIN_LIMIT);
-}
-
-enum replay_gain_mode replay_gain_get_real_mode(void)
-{
- enum replay_gain_mode rgm;
-
- rgm = replay_gain_mode;
-
- if (rgm == REPLAY_GAIN_AUTO)
- rgm = g_playlist.queue.random ? REPLAY_GAIN_TRACK : REPLAY_GAIN_ALBUM;
-
- return rgm;
-}
diff --git a/src/replay_gain_config.h b/src/replay_gain_config.h
index 18747cef2..4b59334c0 100644
--- a/src/replay_gain_config.h
+++ b/src/replay_gain_config.h
@@ -30,6 +30,10 @@ extern float replay_gain_preamp;
extern float replay_gain_missing_preamp;
extern bool replay_gain_limit;
+#ifdef __cplusplus
+extern "C" {
+#endif
+
void replay_gain_global_init(void);
/**
@@ -50,6 +54,10 @@ replay_gain_set_mode_string(const char *p);
* Returns the "real" mode according to the "auto" setting"
*/
enum replay_gain_mode
-replay_gain_get_real_mode(void);
+replay_gain_get_real_mode(bool random_mode);
+
+#ifdef __cplusplus
+}
+#endif
#endif
diff --git a/src/replay_gain_info.c b/src/replay_gain_info.c
deleted file mode 100644
index 1f09e7a1a..000000000
--- a/src/replay_gain_info.c
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "replay_gain_info.h"
-
-float
-replay_gain_tuple_scale(const struct replay_gain_tuple *tuple, float preamp, float missing_preamp, bool peak_limit)
-{
- float scale;
-
- if (replay_gain_tuple_defined(tuple)) {
- scale = pow(10.0, tuple->gain / 20.0);
- scale *= preamp;
- if (scale > 15.0)
- scale = 15.0;
-
- if (peak_limit && scale * tuple->peak > 1.0)
- scale = 1.0 / tuple->peak;
- } else
- scale = missing_preamp;
-
- return scale;
-}
-
-void
-replay_gain_info_complete(struct replay_gain_info *info)
-{
- if (!replay_gain_tuple_defined(&info->tuples[REPLAY_GAIN_ALBUM]))
- info->tuples[REPLAY_GAIN_ALBUM] =
- info->tuples[REPLAY_GAIN_TRACK];
-}
diff --git a/src/replay_gain_info.h b/src/replay_gain_info.h
index 9097c3e02..b06dc6cf0 100644
--- a/src/replay_gain_info.h
+++ b/src/replay_gain_info.h
@@ -22,8 +22,12 @@
#include "check.h"
+#ifdef __cplusplus
+#include <cmath>
+#else
#include <stdbool.h>
#include <math.h>
+#endif
enum replay_gain_mode {
REPLAY_GAIN_AUTO = -2,
@@ -58,9 +62,17 @@ replay_gain_info_init(struct replay_gain_info *info)
static inline bool
replay_gain_tuple_defined(const struct replay_gain_tuple *tuple)
{
+#ifdef __cplusplus
+ return !std::isinf(tuple->gain);
+#else
return !isinf(tuple->gain);
+#endif
}
+#ifdef __cplusplus
+extern "C" {
+#endif
+
float
replay_gain_tuple_scale(const struct replay_gain_tuple *tuple, float preamp, float missing_preamp, bool peak_limit);
@@ -71,4 +83,8 @@ replay_gain_tuple_scale(const struct replay_gain_tuple *tuple, float preamp, flo
void
replay_gain_info_complete(struct replay_gain_info *info);
+#ifdef __cplusplus
+}
+#endif
+
#endif
diff --git a/src/resolver.h b/src/resolver.h
index e5ad06754..af14f5f23 100644
--- a/src/resolver.h
+++ b/src/resolver.h
@@ -20,18 +20,24 @@
#ifndef MPD_RESOLVER_H
#define MPD_RESOLVER_H
+#include "gcc.h"
+
#include <glib.h>
struct sockaddr;
struct addrinfo;
-G_GNUC_CONST
+gcc_const
static inline GQuark
resolver_quark(void)
{
return g_quark_from_static_string("resolver");
}
+#ifdef __cplusplus
+extern "C" {
+#endif
+
/**
* Converts the specified socket address into a string in the form
* "IP:PORT". The return value must be freed with g_free() when you
@@ -42,7 +48,7 @@ resolver_quark(void)
* @param error location to store the error occurring, or NULL to
* ignore errors
*/
-G_GNUC_MALLOC
+gcc_malloc
char *
sockaddr_to_string(const struct sockaddr *sa, size_t length, GError **error);
@@ -61,4 +67,8 @@ resolve_host_port(const char *host_port, unsigned default_port,
int flags, int socktype,
GError **error_r);
+#ifdef __cplusplus
+}
+#endif
+
#endif
diff --git a/src/server_socket.c b/src/server_socket.c
deleted file mode 100644
index 396399596..000000000
--- a/src/server_socket.c
+++ /dev/null
@@ -1,483 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-
-#ifdef HAVE_STRUCT_UCRED
-#define _GNU_SOURCE 1
-#endif
-
-#include "server_socket.h"
-#include "socket_util.h"
-#include "resolver.h"
-#include "fd_util.h"
-#include "glib_socket.h"
-
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <string.h>
-#include <errno.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <assert.h>
-
-#ifdef WIN32
-#include <ws2tcpip.h>
-#include <winsock.h>
-#else
-#include <netinet/in.h>
-#include <sys/socket.h>
-#include <sys/un.h>
-#include <netdb.h>
-#endif
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "listen"
-
-#define DEFAULT_PORT 6600
-
-struct one_socket {
- struct one_socket *next;
- struct server_socket *parent;
-
- unsigned serial;
-
- int fd;
- guint source_id;
-
- char *path;
-
- size_t address_length;
- struct sockaddr address;
-};
-
-struct server_socket {
- server_socket_callback_t callback;
- void *callback_ctx;
-
- struct one_socket *sockets, **sockets_tail_r;
- unsigned next_serial;
-};
-
-static GQuark
-server_socket_quark(void)
-{
- return g_quark_from_static_string("server_socket");
-}
-
-struct server_socket *
-server_socket_new(server_socket_callback_t callback, void *callback_ctx)
-{
- struct server_socket *ss = g_new(struct server_socket, 1);
- ss->callback = callback;
- ss->callback_ctx = callback_ctx;
- ss->sockets = NULL;
- ss->sockets_tail_r = &ss->sockets;
- ss->next_serial = 1;
- return ss;
-}
-
-void
-server_socket_free(struct server_socket *ss)
-{
- server_socket_close(ss);
-
- while (ss->sockets != NULL) {
- struct one_socket *s = ss->sockets;
- ss->sockets = s->next;
-
- assert(s->fd < 0);
-
- g_free(s->path);
- g_free(s);
- }
-
- g_free(ss);
-}
-
-/**
- * Wraper for sockaddr_to_string() which never fails.
- */
-static char *
-one_socket_to_string(const struct one_socket *s)
-{
- char *p = sockaddr_to_string(&s->address, s->address_length, NULL);
- if (p == NULL)
- p = g_strdup("[unknown]");
- return p;
-}
-
-static int
-get_remote_uid(int fd)
-{
-#ifdef HAVE_STRUCT_UCRED
- struct ucred cred;
- socklen_t len = sizeof (cred);
-
- if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) < 0)
- return 0;
-
- return cred.uid;
-#else
-#ifdef HAVE_GETPEEREID
- uid_t euid;
- gid_t egid;
-
- if (getpeereid(fd, &euid, &egid) == 0)
- return euid;
-#else
- (void)fd;
-#endif
- return -1;
-#endif
-}
-
-static gboolean
-server_socket_in_event(G_GNUC_UNUSED GIOChannel *source,
- G_GNUC_UNUSED GIOCondition condition,
- gpointer data)
-{
- struct one_socket *s = data;
-
- struct sockaddr_storage address;
- size_t address_length = sizeof(address);
- int fd = accept_cloexec_nonblock(s->fd, (struct sockaddr*)&address,
- &address_length);
- if (fd >= 0) {
- if (socket_keepalive(fd))
- g_warning("Could not set TCP keepalive option: %s",
- g_strerror(errno));
- s->parent->callback(fd, (const struct sockaddr*)&address,
- address_length, get_remote_uid(fd),
- s->parent->callback_ctx);
- } else {
- g_warning("accept() failed: %s", g_strerror(errno));
- }
-
- return true;
-}
-
-static void
-set_fd(struct one_socket *s, int fd)
-{
- assert(s != NULL);
- assert(s->fd < 0);
- assert(fd >= 0);
-
- s->fd = fd;
-
- GIOChannel *channel = g_io_channel_new_socket(s->fd);
- s->source_id = g_io_add_watch(channel, G_IO_IN,
- server_socket_in_event, s);
- g_io_channel_unref(channel);
-}
-
-bool
-server_socket_open(struct server_socket *ss, GError **error_r)
-{
- struct one_socket *good = NULL, *bad = NULL;
- GError *last_error = NULL;
-
- for (struct one_socket *s = ss->sockets; s != NULL; s = s->next) {
- assert(s->serial > 0);
- assert(good == NULL || s->serial >= good->serial);
- assert(s->fd < 0);
-
- if (bad != NULL && s->serial != bad->serial) {
- server_socket_close(ss);
- g_propagate_error(error_r, last_error);
- return false;
- }
-
- GError *error = NULL;
- int fd = socket_bind_listen(s->address.sa_family,
- SOCK_STREAM, 0,
- &s->address, s->address_length, 5,
- &error);
- if (fd < 0) {
- if (good != NULL && good->serial == s->serial) {
- char *address_string = one_socket_to_string(s);
- char *good_string = one_socket_to_string(good);
- g_warning("bind to '%s' failed: %s "
- "(continuing anyway, because "
- "binding to '%s' succeeded)",
- address_string, error->message,
- good_string);
- g_free(address_string);
- g_free(good_string);
- g_error_free(error);
- } else if (bad == NULL) {
- bad = s;
-
- char *address_string = one_socket_to_string(s);
- g_propagate_prefixed_error(&last_error, error,
- "Failed to bind to '%s': ",
- address_string);
- g_free(address_string);
- } else
- g_error_free(error);
- continue;
- }
-
- /* allow everybody to connect */
-
- if (s->path != NULL)
- chmod(s->path, 0666);
-
- /* register in the GLib main loop */
-
- set_fd(s, fd);
-
- /* mark this socket as "good", and clear previous
- errors */
-
- good = s;
-
- if (bad != NULL) {
- bad = NULL;
- g_error_free(last_error);
- last_error = NULL;
- }
- }
-
- if (bad != NULL) {
- server_socket_close(ss);
- g_propagate_error(error_r, last_error);
- return false;
- }
-
- return true;
-}
-
-void
-server_socket_close(struct server_socket *ss)
-{
- for (struct one_socket *s = ss->sockets; s != NULL; s = s->next) {
- if (s->fd < 0)
- continue;
-
- g_source_remove(s->source_id);
- close_socket(s->fd);
- s->fd = -1;
- }
-}
-
-static struct one_socket *
-one_socket_new(unsigned serial, const struct sockaddr *address,
- size_t address_length)
-{
- assert(address != NULL);
- assert(address_length > 0);
-
- struct one_socket *s = g_malloc(sizeof(*s) - sizeof(s->address) +
- address_length);
- s->next = NULL;
- s->serial = serial;
- s->fd = -1;
- s->path = NULL;
- s->address_length = address_length;
- memcpy(&s->address, address, address_length);
-
- return s;
-}
-
-bool
-server_socket_add_fd(struct server_socket *ss, int fd, GError **error_r)
-{
- assert(ss != NULL);
- assert(ss->sockets_tail_r != NULL);
- assert(*ss->sockets_tail_r == NULL);
- assert(fd >= 0);
-
- struct sockaddr_storage address;
- socklen_t address_length;
- if (getsockname(fd, (struct sockaddr *)&address,
- &address_length) < 0) {
- g_set_error(error_r, server_socket_quark(), errno,
- "Failed to get socket address: %s",
- g_strerror(errno));
- return false;
- }
-
- struct one_socket *s = one_socket_new(ss->next_serial,
- (struct sockaddr *)&address,
- address_length);
- s->parent = ss;
- *ss->sockets_tail_r = s;
- ss->sockets_tail_r = &s->next;
-
- set_fd(s, fd);
-
- return true;
-}
-
-static struct one_socket *
-server_socket_add_address(struct server_socket *ss,
- const struct sockaddr *address,
- size_t address_length)
-{
- assert(ss != NULL);
- assert(ss->sockets_tail_r != NULL);
- assert(*ss->sockets_tail_r == NULL);
-
- struct one_socket *s = one_socket_new(ss->next_serial,
- address, address_length);
- s->parent = ss;
- *ss->sockets_tail_r = s;
- ss->sockets_tail_r = &s->next;
-
- return s;
-}
-
-#ifdef HAVE_TCP
-
-/**
- * Add a listener on a port on all IPv4 interfaces.
- *
- * @param port the TCP port
- */
-static void
-server_socket_add_port_ipv4(struct server_socket *ss, unsigned port)
-{
- struct sockaddr_in sin;
- memset(&sin, 0, sizeof(sin));
- sin.sin_port = htons(port);
- sin.sin_family = AF_INET;
- sin.sin_addr.s_addr = INADDR_ANY;
-
- server_socket_add_address(ss, (const struct sockaddr *)&sin,
- sizeof(sin));
-}
-
-#ifdef HAVE_IPV6
-/**
- * Add a listener on a port on all IPv6 interfaces.
- *
- * @param port the TCP port
- */
-static void
-server_socket_add_port_ipv6(struct server_socket *ss, unsigned port)
-{
- struct sockaddr_in6 sin;
- memset(&sin, 0, sizeof(sin));
- sin.sin6_port = htons(port);
- sin.sin6_family = AF_INET6;
-
- server_socket_add_address(ss, (const struct sockaddr *)&sin,
- sizeof(sin));
-}
-#endif /* HAVE_IPV6 */
-
-#endif /* HAVE_TCP */
-
-bool
-server_socket_add_port(struct server_socket *ss, unsigned port,
- GError **error_r)
-{
-#ifdef HAVE_TCP
- if (port == 0 || port > 0xffff) {
- g_set_error(error_r, server_socket_quark(), 0,
- "Invalid TCP port");
- return false;
- }
-
-#ifdef HAVE_IPV6
- server_socket_add_port_ipv6(ss, port);
-#endif
- server_socket_add_port_ipv4(ss, port);
-
- ++ss->next_serial;
-
- return true;
-#else /* HAVE_TCP */
- (void)ss;
- (void)port;
-
- g_set_error(error_r, server_socket_quark(), 0,
- "TCP support is disabled");
- return false;
-#endif /* HAVE_TCP */
-}
-
-bool
-server_socket_add_host(struct server_socket *ss, const char *hostname,
- unsigned port, GError **error_r)
-{
-#ifdef HAVE_TCP
- struct addrinfo *ai = resolve_host_port(hostname, port,
- AI_PASSIVE, SOCK_STREAM,
- error_r);
- if (ai == NULL)
- return false;
-
- for (const struct addrinfo *i = ai; i != NULL; i = i->ai_next)
- server_socket_add_address(ss, i->ai_addr, i->ai_addrlen);
-
- freeaddrinfo(ai);
-
- ++ss->next_serial;
-
- return true;
-#else /* HAVE_TCP */
- (void)ss;
- (void)hostname;
- (void)port;
-
- g_set_error(error_r, server_socket_quark(), 0,
- "TCP support is disabled");
- return false;
-#endif /* HAVE_TCP */
-}
-
-bool
-server_socket_add_path(struct server_socket *ss, const char *path,
- GError **error_r)
-{
-#ifdef HAVE_UN
- struct sockaddr_un s_un;
-
- size_t path_length = strlen(path);
- if (path_length >= sizeof(s_un.sun_path)) {
- g_set_error(error_r, server_socket_quark(), 0,
- "UNIX socket path is too long");
- return false;
- }
-
- unlink(path);
-
- s_un.sun_family = AF_UNIX;
- memcpy(s_un.sun_path, path, path_length + 1);
-
- struct one_socket *s =
- server_socket_add_address(ss, (const struct sockaddr *)&s_un,
- sizeof(s_un));
- s->path = g_strdup(path);
-
- return true;
-#else /* !HAVE_UN */
- (void)ss;
- (void)path;
-
- g_set_error(error_r, server_socket_quark(), 0,
- "UNIX domain socket support is disabled");
- return false;
-#endif /* !HAVE_UN */
-}
-
diff --git a/src/server_socket.h b/src/server_socket.h
deleted file mode 100644
index 7caa4bbf2..000000000
--- a/src/server_socket.h
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * 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_SERVER_SOCKET_H
-#define MPD_SERVER_SOCKET_H
-
-#include <stdbool.h>
-
-#include <glib.h>
-
-struct sockaddr;
-
-typedef void (*server_socket_callback_t)(int fd,
- const struct sockaddr *address,
- size_t address_length, int uid,
- void *ctx);
-
-struct server_socket *
-server_socket_new(server_socket_callback_t callback, void *callback_ctx);
-
-void
-server_socket_free(struct server_socket *ss);
-
-bool
-server_socket_open(struct server_socket *ss, GError **error_r);
-
-void
-server_socket_close(struct server_socket *ss);
-
-/**
- * Add a socket descriptor that is accepting connections. After this
- * has been called, don't call server_socket_open(), because the
- * socket is already open.
- */
-bool
-server_socket_add_fd(struct server_socket *ss, int fd, GError **error_r);
-
-/**
- * Add a listener on a port on all interfaces.
- *
- * @param port the TCP port
- * @param error_r location to store the error occurring, or NULL to
- * ignore errors
- * @return true on success
- */
-bool
-server_socket_add_port(struct server_socket *ss, unsigned port,
- GError **error_r);
-
-/**
- * Resolves a host name, and adds listeners on all addresses in the
- * result set.
- *
- * @param hostname the host name to be resolved
- * @param port the TCP port
- * @param error_r location to store the error occurring, or NULL to
- * ignore errors
- * @return true on success
- */
-bool
-server_socket_add_host(struct server_socket *ss, const char *hostname,
- unsigned port, GError **error_r);
-
-/**
- * Add a listener on a Unix domain socket.
- *
- * @param path the absolute socket path
- * @param error_r location to store the error occurring, or NULL to
- * ignore errors
- * @return true on success
- */
-bool
-server_socket_add_path(struct server_socket *ss, const char *path,
- GError **error_r);
-
-#endif
diff --git a/src/sig_handlers.c b/src/sig_handlers.c
deleted file mode 100644
index b23f9e778..000000000
--- a/src/sig_handlers.c
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "sig_handlers.h"
-
-#ifndef WIN32
-
-#include "log.h"
-#include "main.h"
-#include "event_pipe.h"
-#include "mpd_error.h"
-
-#include <glib.h>
-
-#include <signal.h>
-#include <errno.h>
-#include <string.h>
-
-static void exit_signal_handler(G_GNUC_UNUSED int signum)
-{
- g_main_loop_quit(main_loop);
-}
-
-static void reload_signal_handler(G_GNUC_UNUSED int signum)
-{
- event_pipe_emit_fast(PIPE_EVENT_RELOAD);
-}
-
-static void
-x_sigaction(int signum, const struct sigaction *act)
-{
- if (sigaction(signum, act, NULL) < 0)
- MPD_ERROR("sigaction() failed: %s", strerror(errno));
-}
-
-static void
-handle_reload_event(void)
-{
- g_debug("got SIGHUP, reopening log files");
- cycle_log_files();
-}
-
-#endif
-
-void initSigHandlers(void)
-{
-#ifndef WIN32
- struct sigaction sa;
-
- sa.sa_flags = 0;
- sigemptyset(&sa.sa_mask);
- sa.sa_handler = SIG_IGN;
- x_sigaction(SIGPIPE, &sa);
-
- sa.sa_handler = exit_signal_handler;
- x_sigaction(SIGINT, &sa);
- x_sigaction(SIGTERM, &sa);
-
- event_pipe_register(PIPE_EVENT_RELOAD, handle_reload_event);
- sa.sa_handler = reload_signal_handler;
- x_sigaction(SIGHUP, &sa);
-#endif
-}
diff --git a/src/sig_handlers.h b/src/sig_handlers.h
deleted file mode 100644
index 32e9bad95..000000000
--- a/src/sig_handlers.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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_SIG_HANDLERS_H
-#define MPD_SIG_HANDLERS_H
-
-void initSigHandlers(void);
-
-#endif
diff --git a/src/socket_util.c b/src/socket_util.c
deleted file mode 100644
index a06a0cbd5..000000000
--- a/src/socket_util.c
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "socket_util.h"
-#include "fd_util.h"
-
-#include <errno.h>
-#include <unistd.h>
-
-#ifndef G_OS_WIN32
-#include <sys/socket.h>
-#else /* G_OS_WIN32 */
-#include <ws2tcpip.h>
-#include <winsock.h>
-#endif /* G_OS_WIN32 */
-
-#ifdef HAVE_IPV6
-#include <string.h>
-#endif
-
-static GQuark
-listen_quark(void)
-{
- return g_quark_from_static_string("listen");
-}
-
-int
-socket_bind_listen(int domain, int type, int protocol,
- const struct sockaddr *address, size_t address_length,
- int backlog,
- GError **error)
-{
- int fd, ret;
- const int reuse = 1;
-
- fd = socket_cloexec_nonblock(domain, type, protocol);
- if (fd < 0) {
- g_set_error(error, listen_quark(), errno,
- "Failed to create socket: %s", g_strerror(errno));
- return -1;
- }
-
- ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
- (const char *) &reuse, sizeof(reuse));
- if (ret < 0) {
- g_set_error(error, listen_quark(), errno,
- "setsockopt() failed: %s", g_strerror(errno));
- close_socket(fd);
- return -1;
- }
-
- ret = bind(fd, address, address_length);
- if (ret < 0) {
- g_set_error(error, listen_quark(), errno,
- "%s", g_strerror(errno));
- close_socket(fd);
- return -1;
- }
-
- ret = listen(fd, backlog);
- if (ret < 0) {
- g_set_error(error, listen_quark(), errno,
- "listen() failed: %s", g_strerror(errno));
- close_socket(fd);
- return -1;
- }
-
-#ifdef HAVE_STRUCT_UCRED
- setsockopt(fd, SOL_SOCKET, SO_PASSCRED,
- (const char *) &reuse, sizeof(reuse));
-#endif
-
- return fd;
-}
-
-int
-socket_keepalive(int fd)
-{
- const int reuse = 1;
-
- return setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE,
- (const char *)&reuse, sizeof(reuse));
-}
diff --git a/src/socket_util.h b/src/socket_util.h
deleted file mode 100644
index 93bd27362..000000000
--- a/src/socket_util.h
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * This library provides easy helper functions for working with
- * sockets.
- *
- */
-
-#ifndef SOCKET_UTIL_H
-#define SOCKET_UTIL_H
-
-#include <glib.h>
-
-struct sockaddr;
-
-/**
- * Creates a socket listening on the specified address. This is a
- * shortcut for socket(), bind() and listen().
- *
- * @param domain the socket domain, e.g. PF_INET6
- * @param type the socket type, e.g. SOCK_STREAM
- * @param protocol the protocol, usually 0 to let the kernel choose
- * @param address the address to listen on
- * @param address_length the size of #address
- * @param backlog the backlog parameter for the listen() system call
- * @param error location to store the error occurring, or NULL to
- * ignore errors
- * @return the socket file descriptor or -1 on error
- */
-int
-socket_bind_listen(int domain, int type, int protocol,
- const struct sockaddr *address, size_t address_length,
- int backlog,
- GError **error);
-
-int
-socket_keepalive(int fd);
-
-#endif
diff --git a/src/song.c b/src/song.c
deleted file mode 100644
index f5cc7c35a..000000000
--- a/src/song.c
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "song.h"
-#include "uri.h"
-#include "directory.h"
-#include "tag.h"
-
-#include <glib.h>
-
-#include <assert.h>
-
-static struct song *
-song_alloc(const char *uri, struct directory *parent)
-{
- size_t uri_length;
- struct song *song;
-
- assert(uri);
- uri_length = strlen(uri);
- assert(uri_length);
- song = g_malloc(sizeof(*song) - sizeof(song->uri) + uri_length + 1);
-
- song->tag = NULL;
- memcpy(song->uri, uri, uri_length + 1);
- song->parent = parent;
- song->mtime = 0;
- song->start_ms = song->end_ms = 0;
-
- return song;
-}
-
-struct song *
-song_remote_new(const char *uri)
-{
- return song_alloc(uri, NULL);
-}
-
-struct song *
-song_file_new(const char *path, struct directory *parent)
-{
- assert((parent == NULL) == (*path == '/'));
-
- return song_alloc(path, parent);
-}
-
-struct song *
-song_replace_uri(struct song *old_song, const char *uri)
-{
- struct song *new_song = song_alloc(uri, old_song->parent);
- new_song->tag = old_song->tag;
- new_song->mtime = old_song->mtime;
- new_song->start_ms = old_song->start_ms;
- new_song->end_ms = old_song->end_ms;
- g_free(old_song);
- return new_song;
-}
-
-void
-song_free(struct song *song)
-{
- if (song->tag)
- tag_free(song->tag);
- g_free(song);
-}
-
-char *
-song_get_uri(const struct song *song)
-{
- assert(song != NULL);
- assert(*song->uri);
-
- if (!song_in_database(song) || directory_is_root(song->parent))
- return g_strdup(song->uri);
- else
- return g_strconcat(directory_get_path(song->parent),
- "/", song->uri, NULL);
-}
-
-double
-song_get_duration(const struct song *song)
-{
- if (song->end_ms > 0)
- return (song->end_ms - song->start_ms) / 1000.0;
-
- if (song->tag == NULL)
- return 0;
-
- return song->tag->time - song->start_ms / 1000.0;
-}
diff --git a/src/song.h b/src/song.h
deleted file mode 100644
index 8b97d45d0..000000000
--- a/src/song.h
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * 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_SONG_H
-#define MPD_SONG_H
-
-#include "util/list.h"
-
-#include <stddef.h>
-#include <stdbool.h>
-#include <sys/time.h>
-
-#define SONG_FILE "file: "
-#define SONG_TIME "Time: "
-
-struct song {
- /**
- * Pointers to the siblings of this directory within the
- * parent directory. It is unused (undefined) if this song is
- * not in the database.
- *
- * This attribute is protected with the global #db_mutex.
- * Read access in the update thread does not need protection.
- */
- struct list_head siblings;
-
- struct tag *tag;
- struct directory *parent;
- 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;
-
- char uri[sizeof(int)];
-};
-
-/** allocate a new song with a remote URL */
-struct song *
-song_remote_new(const char *uri);
-
-/** allocate a new song with a local file name */
-struct song *
-song_file_new(const char *path, struct directory *parent);
-
-/**
- * allocate a new song structure with a local file name and attempt to
- * load its metadata. If all decoder plugin fail to read its meta
- * data, NULL is returned.
- */
-struct song *
-song_file_load(const char *path, struct directory *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.
- */
-struct song *
-song_replace_uri(struct song *song, const char *uri);
-
-void
-song_free(struct song *song);
-
-bool
-song_file_update(struct song *song);
-
-bool
-song_file_update_inarchive(struct song *song);
-
-/**
- * Returns the URI of the song in UTF-8 encoding, including its
- * location within the music directory.
- *
- * The return value is allocated on the heap, and must be freed by the
- * caller.
- */
-char *
-song_get_uri(const struct song *song);
-
-double
-song_get_duration(const struct song *song);
-
-static inline bool
-song_in_database(const struct song *song)
-{
- return song->parent != NULL;
-}
-
-static inline bool
-song_is_file(const struct song *song)
-{
- return song_in_database(song) || song->uri[0] == '/';
-}
-
-#endif
diff --git a/src/song_print.c b/src/song_print.c
deleted file mode 100644
index fb608a8b2..000000000
--- a/src/song_print.c
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "song_print.h"
-#include "song.h"
-#include "directory.h"
-#include "tag_print.h"
-#include "client.h"
-#include "uri.h"
-#include "mapper.h"
-
-void
-song_print_uri(struct client *client, struct song *song)
-{
- if (song_in_database(song) && !directory_is_root(song->parent)) {
- client_printf(client, "%s%s/%s\n", SONG_FILE,
- directory_get_path(song->parent), song->uri);
- } else {
- char *allocated;
- const char *uri;
-
- uri = allocated = uri_remove_auth(song->uri);
- if (uri == NULL)
- uri = song->uri;
-
- client_printf(client, "%s%s\n", SONG_FILE,
- map_to_relative_path(uri));
-
- g_free(allocated);
- }
-}
-
-void
-song_print_info(struct client *client, struct song *song)
-{
- song_print_uri(client, song);
-
- if (song->end_ms > 0)
- client_printf(client, "Range: %u.%03u-%u.%03u\n",
- song->start_ms / 1000,
- song->start_ms % 1000,
- song->end_ms / 1000,
- song->end_ms % 1000);
- else if (song->start_ms > 0)
- client_printf(client, "Range: %u.%03u-\n",
- song->start_ms / 1000,
- song->start_ms % 1000);
-
- if (song->mtime > 0) {
-#ifndef G_OS_WIN32
- struct tm tm;
-#endif
- const struct tm *tm2;
-
-#ifdef G_OS_WIN32
- tm2 = gmtime(&song->mtime);
-#else
- tm2 = gmtime_r(&song->mtime, &tm);
-#endif
-
- if (tm2 != NULL) {
- char timestamp[32];
-
- strftime(timestamp, sizeof(timestamp),
-#ifdef G_OS_WIN32
- "%Y-%m-%dT%H:%M:%SZ",
-#else
- "%FT%TZ",
-#endif
- tm2);
- client_printf(client, "Last-Modified: %s\n",
- timestamp);
- }
- }
-
- if (song->tag)
- tag_print(client, song->tag);
-}
diff --git a/src/song_print.h b/src/song_print.h
deleted file mode 100644
index 8f1f0cc65..000000000
--- a/src/song_print.h
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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_SONG_PRINT_H
-#define MPD_SONG_PRINT_H
-
-struct client;
-struct song;
-struct songvec;
-
-void
-song_print_info(struct client *client, struct song *song);
-
-void
-song_print_uri(struct client *client, struct song *song);
-
-#endif
diff --git a/src/song_save.c b/src/song_save.c
deleted file mode 100644
index 4fcb46e22..000000000
--- a/src/song_save.c
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "song_save.h"
-#include "song.h"
-#include "tag_save.h"
-#include "directory.h"
-#include "tag.h"
-#include "text_file.h"
-#include "string_util.h"
-
-#include <glib.h>
-
-#include <stdlib.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "song"
-
-#define SONG_MTIME "mtime"
-#define SONG_END "song_end"
-
-static GQuark
-song_save_quark(void)
-{
- return g_quark_from_static_string("song_save");
-}
-
-void
-song_save(FILE *fp, const struct 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);
-
- if (song->tag != NULL)
- tag_save(fp, song->tag);
-
- fprintf(fp, SONG_MTIME ": %li\n", (long)song->mtime);
- fprintf(fp, SONG_END "\n");
-}
-
-struct song *
-song_load(FILE *fp, struct directory *parent, const char *uri,
- GString *buffer, GError **error_r)
-{
- struct song *song = parent != NULL
- ? song_file_new(uri, parent)
- : song_remote_new(uri);
- char *line, *colon;
- enum tag_type type;
- const char *value;
-
- while ((line = read_text_line(fp, buffer)) != NULL &&
- strcmp(line, SONG_END) != 0) {
- colon = strchr(line, ':');
- if (colon == NULL || colon == line) {
- if (song->tag != NULL)
- tag_end_add(song->tag);
- song_free(song);
-
- g_set_error(error_r, song_save_quark(), 0,
- "unknown line in db: %s", line);
- return NULL;
- }
-
- *colon++ = 0;
- value = strchug_fast_c(colon);
-
- if ((type = tag_name_parse(line)) != TAG_NUM_OF_ITEM_TYPES) {
- if (!song->tag) {
- song->tag = tag_new();
- tag_begin_add(song->tag);
- }
-
- tag_add_item(song->tag, type, value);
- } else if (strcmp(line, "Time") == 0) {
- if (!song->tag) {
- song->tag = tag_new();
- tag_begin_add(song->tag);
- }
-
- song->tag->time = atoi(value);
- } else if (strcmp(line, "Playlist") == 0) {
- if (!song->tag) {
- song->tag = tag_new();
- tag_begin_add(song->tag);
- }
-
- song->tag->has_playlist = strcmp(value, "yes") == 0;
- } else if (strcmp(line, SONG_MTIME) == 0) {
- song->mtime = 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, NULL, 10);
- } else {
- if (song->tag != NULL)
- tag_end_add(song->tag);
- song_free(song);
-
- g_set_error(error_r, song_save_quark(), 0,
- "unknown line in db: %s", line);
- return NULL;
- }
- }
-
- if (song->tag != NULL)
- tag_end_add(song->tag);
-
- return song;
-}
diff --git a/src/song_save.h b/src/song_save.h
deleted file mode 100644
index f6ecbbfeb..000000000
--- a/src/song_save.h
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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_SONG_SAVE_H
-#define MPD_SONG_SAVE_H
-
-#include <glib.h>
-
-#include <stdio.h>
-
-#define SONG_BEGIN "song_begin: "
-
-struct song;
-struct directory;
-
-void
-song_save(FILE *fp, const struct song *song);
-
-/**
- * Loads a song from the input file. Reading stops after the
- * "song_end" line.
- *
- * @param error_r location to store the error occurring, or NULL to
- * ignore errors
- * @return true on success, false on error
- */
-struct song *
-song_load(FILE *fp, struct directory *parent, const char *uri,
- GString *buffer, GError **error_r);
-
-#endif
diff --git a/src/song_sort.c b/src/song_sort.c
deleted file mode 100644
index 397d2c7a9..000000000
--- a/src/song_sort.c
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * 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 "song_sort.h"
-#include "song.h"
-#include "util/list.h"
-#include "util/list_sort.h"
-#include "tag.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <stdlib.h>
-
-static const char *
-tag_get_value_checked(const struct tag *tag, enum tag_type type)
-{
- return tag != NULL
- ? tag_get_value(tag, type)
- : NULL;
-}
-
-static int
-compare_utf8_string(const char *a, const char *b)
-{
- if (a == NULL)
- return b == NULL ? 0 : -1;
-
- if (b == NULL)
- return 1;
-
- return g_utf8_collate(a, b);
-}
-
-/**
- * Compare two string tag values, ignoring case. Either one may be
- * NULL.
- */
-static int
-compare_string_tag_item(const struct tag *a, const struct tag *b,
- enum tag_type type)
-{
- return compare_utf8_string(tag_get_value_checked(a, type),
- tag_get_value_checked(b, type));
-}
-
-/**
- * Compare two tag values which should contain an integer value
- * (e.g. disc or track number). Either one may be NULL.
- */
-static int
-compare_number_string(const char *a, const char *b)
-{
- long ai = a == NULL ? 0 : strtol(a, NULL, 10);
- long bi = b == NULL ? 0 : strtol(b, NULL, 10);
-
- if (ai <= 0)
- return bi <= 0 ? 0 : -1;
-
- if (bi <= 0)
- return 1;
-
- return ai - bi;
-}
-
-static int
-compare_tag_item(const struct tag *a, const struct tag *b, enum tag_type type)
-{
- return compare_number_string(tag_get_value_checked(a, type),
- tag_get_value_checked(b, type));
-}
-
-/* Only used for sorting/searchin a songvec, not general purpose compares */
-static int
-song_cmp(G_GNUC_UNUSED void *priv, struct list_head *_a, struct list_head *_b)
-{
- const struct song *a = (const struct song *)_a;
- const struct song *b = (const struct song *)_b;
- int ret;
-
- /* first sort by album */
- ret = compare_string_tag_item(a->tag, b->tag, TAG_ALBUM);
- if (ret != 0)
- return ret;
-
- /* then sort by disc */
- ret = compare_tag_item(a->tag, b->tag, TAG_DISC);
- if (ret != 0)
- return ret;
-
- /* then by track number */
- ret = compare_tag_item(a->tag, b->tag, TAG_TRACK);
- if (ret != 0)
- return ret;
-
- /* still no difference? compare file name */
- return g_utf8_collate(a->uri, b->uri);
-}
-
-void
-song_list_sort(struct list_head *songs)
-{
- list_sort(NULL, songs, song_cmp);
-}
diff --git a/src/song_sort.h b/src/song_sort.h
deleted file mode 100644
index ec124cf4a..000000000
--- a/src/song_sort.h
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef MPD_SONG_SORT_H
-#define MPD_SONG_SORT_H
-
-struct list_head;
-
-void
-song_list_sort(struct list_head *songs);
-
-#endif
diff --git a/src/song_sticker.c b/src/song_sticker.c
deleted file mode 100644
index 78025906e..000000000
--- a/src/song_sticker.c
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "song_sticker.h"
-#include "song.h"
-#include "directory.h"
-#include "sticker.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-
-char *
-sticker_song_get_value(const struct song *song, const char *name)
-{
- char *uri, *value;
-
- assert(song != NULL);
- assert(song_in_database(song));
-
- uri = song_get_uri(song);
- value = sticker_load_value("song", uri, name);
- g_free(uri);
-
- return value;
-}
-
-bool
-sticker_song_set_value(const struct song *song,
- const char *name, const char *value)
-{
- char *uri;
- bool ret;
-
- assert(song != NULL);
- assert(song_in_database(song));
-
- uri = song_get_uri(song);
- ret = sticker_store_value("song", uri, name, value);
- g_free(uri);
-
- return ret;
-}
-
-bool
-sticker_song_delete(const struct song *song)
-{
- char *uri;
- bool ret;
-
- assert(song != NULL);
- assert(song_in_database(song));
-
- uri = song_get_uri(song);
- ret = sticker_delete("song", uri);
- g_free(uri);
-
- return ret;
-}
-
-bool
-sticker_song_delete_value(const struct song *song, const char *name)
-{
- char *uri;
- bool success;
-
- assert(song != NULL);
- assert(song_in_database(song));
-
- uri = song_get_uri(song);
- success = sticker_delete_value("song", uri, name);
- g_free(uri);
-
- return success;
-}
-
-struct sticker *
-sticker_song_get(const struct song *song)
-{
- char *uri;
- struct sticker *sticker;
-
- assert(song != NULL);
- assert(song_in_database(song));
-
- uri = song_get_uri(song);
- sticker = sticker_load("song", uri);
- g_free(uri);
-
- return sticker;
-}
-
-struct sticker_song_find_data {
- struct directory *directory;
- const char *base_uri;
- size_t base_uri_length;
-
- void (*func)(struct song *song, const char *value,
- gpointer user_data);
- gpointer user_data;
-};
-
-static void
-sticker_song_find_cb(const char *uri, const char *value, gpointer user_data)
-{
- struct sticker_song_find_data *data = user_data;
- struct song *song;
-
- if (memcmp(uri, data->base_uri, data->base_uri_length) != 0)
- /* should not happen, ignore silently */
- return;
-
- song = directory_lookup_song(data->directory,
- uri + data->base_uri_length);
- if (song != NULL)
- data->func(song, value, data->user_data);
-}
-
-bool
-sticker_song_find(struct directory *directory, const char *name,
- void (*func)(struct song *song, const char *value,
- gpointer user_data),
- gpointer user_data)
-{
- struct sticker_song_find_data data = {
- .directory = directory,
- .func = func,
- .user_data = user_data,
- };
- char *allocated;
- bool success;
-
- data.base_uri = directory_get_path(directory);
- if (*data.base_uri != 0)
- /* append slash to base_uri */
- data.base_uri = allocated =
- g_strconcat(data.base_uri, "/", NULL);
- else
- /* searching in root directory - no trailing slash */
- allocated = NULL;
-
- data.base_uri_length = strlen(data.base_uri);
-
- success = sticker_find("song", data.base_uri, name,
- sticker_song_find_cb, &data);
- g_free(allocated);
-
- return success;
-}
diff --git a/src/song_sticker.h b/src/song_sticker.h
deleted file mode 100644
index 20ae68ce9..000000000
--- a/src/song_sticker.h
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * 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 SONG_STICKER_H
-#define SONG_STICKER_H
-
-#include <stdbool.h>
-#include <glib.h>
-
-struct song;
-struct directory;
-struct sticker;
-
-/**
- * Returns one value from a song's sticker record. The caller must
- * free the return value with g_free().
- */
-char *
-sticker_song_get_value(const struct song *song, const char *name);
-
-/**
- * Sets a sticker value in the specified song. Overwrites existing
- * values.
- */
-bool
-sticker_song_set_value(const struct song *song,
- const char *name, const char *value);
-
-/**
- * Deletes a sticker from the database. All values are deleted.
- */
-bool
-sticker_song_delete(const struct song *song);
-
-/**
- * Deletes a sticker value. Does nothing if the sticker did not
- * exist.
- */
-bool
-sticker_song_delete_value(const struct song *song, const char *name);
-
-/**
- * Loads the sticker for the specified song.
- *
- * @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 struct song *song);
-
-/**
- * Finds stickers with the specified name below the specified
- * directory.
- *
- * Caller must lock the #db_mutex.
- *
- * @param directory the base directory to search in
- * @param name the name of the sticker
- * @return true on success (even if no sticker was found), false on
- * failure
- */
-bool
-sticker_song_find(struct directory *directory, const char *name,
- void (*func)(struct song *song, const char *value,
- gpointer user_data),
- gpointer user_data);
-
-#endif
diff --git a/src/song_update.c b/src/song_update.c
deleted file mode 100644
index f9d6b6f46..000000000
--- a/src/song_update.c
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h" /* must be first for large file support */
-#include "song.h"
-#include "uri.h"
-#include "directory.h"
-#include "mapper.h"
-#include "decoder_list.h"
-#include "decoder_plugin.h"
-#include "tag_ape.h"
-#include "tag_id3.h"
-#include "tag.h"
-#include "tag_handler.h"
-#include "input_stream.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <stdio.h>
-
-struct song *
-song_file_load(const char *path, struct directory *parent)
-{
- struct song *song;
- bool ret;
-
- assert((parent == NULL) == g_path_is_absolute(path));
- assert(!uri_has_scheme(path));
- assert(strchr(path, '\n') == NULL);
-
- song = song_file_new(path, parent);
-
- //in archive ?
- if (parent != NULL && parent->device == DEVICE_INARCHIVE) {
- ret = song_file_update_inarchive(song);
- } else {
- ret = song_file_update(song);
- }
- if (!ret) {
- song_free(song);
- return NULL;
- }
-
- return song;
-}
-
-/**
- * Attempts to load APE or ID3 tags from the specified file.
- */
-static bool
-tag_scan_fallback(const char *path,
- const struct tag_handler *handler, void *handler_ctx)
-{
- return tag_ape_scan2(path, handler, handler_ctx) ||
- tag_id3_scan(path, handler, handler_ctx);
-}
-
-bool
-song_file_update(struct song *song)
-{
- const char *suffix;
- char *path_fs;
- const struct decoder_plugin *plugin;
- struct stat st;
- struct input_stream *is = NULL;
-
- assert(song_is_file(song));
-
- /* check if there's a suffix and a plugin */
-
- suffix = uri_get_suffix(song->uri);
- if (suffix == NULL)
- return false;
-
- plugin = decoder_plugin_from_suffix(suffix, NULL);
- if (plugin == NULL)
- return false;
-
- path_fs = map_song_fs(song);
- if (path_fs == NULL)
- return false;
-
- if (song->tag != NULL) {
- tag_free(song->tag);
- song->tag = NULL;
- }
-
- if (stat(path_fs, &st) < 0 || !S_ISREG(st.st_mode)) {
- g_free(path_fs);
- return false;
- }
-
- song->mtime = st.st_mtime;
-
- GMutex *mutex = NULL;
- GCond *cond;
-#if !GCC_CHECK_VERSION(4, 2)
- /* work around "may be used uninitialized in this function"
- false positive */
- cond = NULL;
-#endif
-
- do {
- /* load file tag */
- song->tag = tag_new();
- if (decoder_plugin_scan_file(plugin, path_fs,
- &full_tag_handler, song->tag))
- break;
-
- tag_free(song->tag);
- song->tag = NULL;
-
- /* fall back to stream tag */
- if (plugin->scan_stream != NULL) {
- /* open the input_stream (if not already
- open) */
- if (is == NULL) {
- mutex = g_mutex_new();
- cond = g_cond_new();
- is = input_stream_open(path_fs, mutex, cond,
- NULL);
- }
-
- /* now try the stream_tag() method */
- if (is != NULL) {
- song->tag = tag_new();
- if (decoder_plugin_scan_stream(plugin, is,
- &full_tag_handler,
- song->tag))
- break;
-
- tag_free(song->tag);
- song->tag = NULL;
-
- input_stream_lock_seek(is, 0, SEEK_SET, NULL);
- }
- }
-
- plugin = decoder_plugin_from_suffix(suffix, plugin);
- } while (plugin != NULL);
-
- if (is != NULL)
- input_stream_close(is);
-
- if (mutex != NULL) {
- g_cond_free(cond);
- g_mutex_free(mutex);
- }
-
- if (song->tag != NULL && tag_is_empty(song->tag))
- tag_scan_fallback(path_fs, &full_tag_handler, song->tag);
-
- g_free(path_fs);
- return song->tag != NULL;
-}
-
-bool
-song_file_update_inarchive(struct song *song)
-{
- const char *suffix;
- const struct decoder_plugin *plugin;
-
- assert(song_is_file(song));
-
- /* check if there's a suffix and a plugin */
-
- suffix = uri_get_suffix(song->uri);
- if (suffix == NULL)
- return false;
-
- plugin = decoder_plugin_from_suffix(suffix, NULL);
- if (plugin == NULL)
- return false;
-
- if (song->tag != NULL)
- tag_free(song->tag);
-
- //accept every file that has music suffix
- //because we don't support tag reading through
- //input streams
- song->tag = tag_new();
-
- return true;
-}
diff --git a/src/state_file.c b/src/state_file.c
deleted file mode 100644
index de0e70538..000000000
--- a/src/state_file.c
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "state_file.h"
-#include "output_state.h"
-#include "playlist.h"
-#include "playlist_state.h"
-#include "volume.h"
-#include "text_file.h"
-
-#include <glib.h>
-#include <assert.h>
-#include <string.h>
-#include <errno.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "state_file"
-
-static char *state_file_path;
-
-/** the GLib source id for the save timer */
-static guint save_state_source_id;
-
-/**
- * These version numbers determine whether we need to save the state
- * file. If nothing has changed, we won't let the hard drive spin up.
- */
-static unsigned prev_volume_version, prev_output_version,
- prev_playlist_version;
-
-static void
-state_file_write(struct player_control *pc)
-{
- FILE *fp;
-
- assert(state_file_path != NULL);
-
- g_debug("Saving state file %s", state_file_path);
-
- fp = fopen(state_file_path, "w");
- if (G_UNLIKELY(!fp)) {
- g_warning("failed to create %s: %s",
- state_file_path, g_strerror(errno));
- return;
- }
-
- save_sw_volume_state(fp);
- audio_output_state_save(fp);
- playlist_state_save(fp, &g_playlist, pc);
-
- fclose(fp);
-
- prev_volume_version = sw_volume_state_get_hash();
- prev_output_version = audio_output_state_get_version();
- prev_playlist_version = playlist_state_get_hash(&g_playlist, pc);
-}
-
-static void
-state_file_read(struct player_control *pc)
-{
- FILE *fp;
- bool success;
-
- assert(state_file_path != NULL);
-
- g_debug("Loading state file %s", state_file_path);
-
- fp = fopen(state_file_path, "r");
- if (G_UNLIKELY(!fp)) {
- g_warning("failed to open %s: %s",
- state_file_path, g_strerror(errno));
- return;
- }
-
- GString *buffer = g_string_sized_new(1024);
- const char *line;
- while ((line = read_text_line(fp, buffer)) != NULL) {
- success = read_sw_volume_state(line) ||
- audio_output_state_read(line) ||
- playlist_state_restore(line, fp, buffer,
- &g_playlist, pc);
- if (!success)
- g_warning("Unrecognized line in state file: %s", line);
- }
-
- fclose(fp);
-
- prev_volume_version = sw_volume_state_get_hash();
- prev_output_version = audio_output_state_get_version();
- prev_playlist_version = playlist_state_get_hash(&g_playlist, pc);
-
-
- g_string_free(buffer, true);
-}
-
-/**
- * This function is called every 5 minutes by the GLib main loop, and
- * saves the state file.
- */
-static gboolean
-timer_save_state_file(gpointer data)
-{
- struct player_control *pc = data;
-
- if (prev_volume_version == sw_volume_state_get_hash() &&
- prev_output_version == audio_output_state_get_version() &&
- prev_playlist_version == playlist_state_get_hash(&g_playlist, pc))
- /* nothing has changed - don't save the state file,
- don't spin up the hard disk */
- return true;
-
- state_file_write(pc);
- return true;
-}
-
-void
-state_file_init(const char *path, struct player_control *pc)
-{
- assert(state_file_path == NULL);
-
- if (path == NULL)
- return;
-
- state_file_path = g_strdup(path);
- state_file_read(pc);
-
- save_state_source_id = g_timeout_add_seconds(5 * 60,
- timer_save_state_file,
- pc);
-}
-
-void
-state_file_finish(struct player_control *pc)
-{
- if (state_file_path == NULL)
- /* no state file configured, no cleanup required */
- return;
-
- if (save_state_source_id != 0)
- g_source_remove(save_state_source_id);
-
- state_file_write(pc);
-
- g_free(state_file_path);
-}
diff --git a/src/state_file.h b/src/state_file.h
deleted file mode 100644
index 4c4f881cc..000000000
--- a/src/state_file.h
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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_STATE_FILE_H
-#define MPD_STATE_FILE_H
-
-struct player_control;
-
-void
-state_file_init(const char *path, struct player_control *pc);
-
-void
-state_file_finish(struct player_control *pc);
-
-void write_state_file(void);
-
-#endif /* STATE_FILE_H */
diff --git a/src/stats.c b/src/stats.c
deleted file mode 100644
index fe6a064a6..000000000
--- a/src/stats.c
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "stats.h"
-#include "database.h"
-#include "db_visitor.h"
-#include "tag.h"
-#include "song.h"
-#include "client.h"
-#include "player_control.h"
-#include "strset.h"
-#include "client_internal.h"
-
-struct stats stats;
-
-void stats_global_init(void)
-{
- stats.timer = g_timer_new();
-}
-
-void stats_global_finish(void)
-{
- g_timer_destroy(stats.timer);
-}
-
-struct visit_data {
- struct strset *artists;
- struct strset *albums;
-};
-
-static void
-visit_tag(struct visit_data *data, const struct tag *tag)
-{
- if (tag->time > 0)
- stats.song_duration += tag->time;
-
- for (unsigned i = 0; i < tag->num_items; ++i) {
- const struct tag_item *item = tag->items[i];
-
- switch (item->type) {
- case TAG_ARTIST:
- strset_add(data->artists, item->value);
- break;
-
- case TAG_ALBUM:
- strset_add(data->albums, item->value);
- break;
-
- default:
- break;
- }
- }
-}
-
-static bool
-collect_stats_song(struct song *song, void *_data,
- G_GNUC_UNUSED GError **error_r)
-{
- struct visit_data *data = _data;
-
- ++stats.song_count;
-
- if (song->tag != NULL)
- visit_tag(data, song->tag);
-
- return true;
-}
-
-static const struct db_visitor collect_stats_visitor = {
- .song = collect_stats_song,
-};
-
-void stats_update(void)
-{
- struct visit_data data;
-
- stats.song_count = 0;
- stats.song_duration = 0;
- stats.artist_count = 0;
-
- data.artists = strset_new();
- data.albums = strset_new();
-
- db_walk("", &collect_stats_visitor, &data, NULL);
-
- stats.artist_count = strset_size(data.artists);
- stats.album_count = strset_size(data.albums);
-
- strset_free(data.artists);
- strset_free(data.albums);
-}
-
-int stats_print(struct client *client)
-{
- client_printf(client,
- "artists: %u\n"
- "albums: %u\n"
- "songs: %i\n"
- "uptime: %li\n"
- "playtime: %li\n"
- "db_playtime: %li\n"
- "db_update: %li\n",
- stats.artist_count,
- stats.album_count,
- stats.song_count,
- (long)g_timer_elapsed(stats.timer, NULL),
- (long)(pc_get_total_play_time(client->player_control) + 0.5),
- stats.song_duration,
- (long)db_get_mtime());
- return 0;
-}
diff --git a/src/stats.h b/src/stats.h
index a686477de..eb723bcf3 100644
--- a/src/stats.h
+++ b/src/stats.h
@@ -22,7 +22,7 @@
#include <glib.h>
-struct client;
+class Client;
struct stats {
GTimer *timer;
@@ -49,6 +49,7 @@ void stats_global_finish(void);
void stats_update(void);
-int stats_print(struct client *client);
+void
+stats_print(Client *client);
#endif
diff --git a/src/sticker.c b/src/sticker.c
deleted file mode 100644
index 346a827a5..000000000
--- a/src/sticker.c
+++ /dev/null
@@ -1,660 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "sticker.h"
-#include "idle.h"
-
-#include <glib.h>
-#include <sqlite3.h>
-#include <assert.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "sticker"
-
-#if SQLITE_VERSION_NUMBER < 3003009
-#define sqlite3_prepare_v2 sqlite3_prepare
-#endif
-
-struct sticker {
- GHashTable *table;
-};
-
-enum sticker_sql {
- STICKER_SQL_GET,
- STICKER_SQL_LIST,
- STICKER_SQL_UPDATE,
- STICKER_SQL_INSERT,
- STICKER_SQL_DELETE,
- STICKER_SQL_DELETE_VALUE,
- STICKER_SQL_FIND,
-};
-
-static const char *const sticker_sql[] = {
- [STICKER_SQL_GET] =
- "SELECT value FROM sticker WHERE type=? AND uri=? AND name=?",
- [STICKER_SQL_LIST] =
- "SELECT name,value FROM sticker WHERE type=? AND uri=?",
- [STICKER_SQL_UPDATE] =
- "UPDATE sticker SET value=? WHERE type=? AND uri=? AND name=?",
- [STICKER_SQL_INSERT] =
- "INSERT INTO sticker(type,uri,name,value) VALUES(?, ?, ?, ?)",
- [STICKER_SQL_DELETE] =
- "DELETE FROM sticker WHERE type=? AND uri=?",
- [STICKER_SQL_DELETE_VALUE] =
- "DELETE FROM sticker WHERE type=? AND uri=? AND name=?",
- [STICKER_SQL_FIND] =
- "SELECT uri,value FROM sticker WHERE type=? AND uri LIKE (? || '%') AND name=?",
-};
-
-static const char sticker_sql_create[] =
- "CREATE TABLE IF NOT EXISTS sticker("
- " type VARCHAR NOT NULL, "
- " uri VARCHAR NOT NULL, "
- " name VARCHAR NOT NULL, "
- " value VARCHAR NOT NULL"
- ");"
- "CREATE UNIQUE INDEX IF NOT EXISTS"
- " sticker_value ON sticker(type, uri, name);"
- "";
-
-static sqlite3 *sticker_db;
-static sqlite3_stmt *sticker_stmt[G_N_ELEMENTS(sticker_sql)];
-
-static GQuark
-sticker_quark(void)
-{
- return g_quark_from_static_string("sticker");
-}
-
-static sqlite3_stmt *
-sticker_prepare(const char *sql, GError **error_r)
-{
- int ret;
- sqlite3_stmt *stmt;
-
- ret = sqlite3_prepare_v2(sticker_db, sql, -1, &stmt, NULL);
- if (ret != SQLITE_OK) {
- g_set_error(error_r, sticker_quark(), ret,
- "sqlite3_prepare_v2() failed: %s",
- sqlite3_errmsg(sticker_db));
- return NULL;
- }
-
- return stmt;
-}
-
-bool
-sticker_global_init(const char *path, GError **error_r)
-{
- int ret;
-
- if (path == NULL)
- /* not configured */
- return true;
-
- /* open/create the sqlite database */
-
- ret = sqlite3_open(path, &sticker_db);
- if (ret != SQLITE_OK) {
- g_set_error(error_r, sticker_quark(), ret,
- "Failed to open sqlite database '%s': %s",
- path, sqlite3_errmsg(sticker_db));
- return false;
- }
-
- /* create the table and index */
-
- ret = sqlite3_exec(sticker_db, sticker_sql_create, NULL, NULL, NULL);
- if (ret != SQLITE_OK) {
- g_set_error(error_r, sticker_quark(), ret,
- "Failed to create sticker table: %s",
- sqlite3_errmsg(sticker_db));
- return false;
- }
-
- /* prepare the statements we're going to use */
-
- for (unsigned i = 0; i < G_N_ELEMENTS(sticker_sql); ++i) {
- assert(sticker_sql[i] != NULL);
-
- sticker_stmt[i] = sticker_prepare(sticker_sql[i], error_r);
- if (sticker_stmt[i] == NULL)
- return false;
- }
-
- return true;
-}
-
-void
-sticker_global_finish(void)
-{
- if (sticker_db == NULL)
- /* not configured */
- return;
-
- for (unsigned i = 0; i < G_N_ELEMENTS(sticker_stmt); ++i) {
- assert(sticker_stmt[i] != NULL);
-
- sqlite3_finalize(sticker_stmt[i]);
- }
-
- sqlite3_close(sticker_db);
-}
-
-bool
-sticker_enabled(void)
-{
- return sticker_db != NULL;
-}
-
-char *
-sticker_load_value(const char *type, const char *uri, const char *name)
-{
- sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_GET];
- int ret;
- char *value;
-
- assert(sticker_enabled());
- assert(type != NULL);
- assert(uri != NULL);
- assert(name != NULL);
-
- if (*name == 0)
- return NULL;
-
- sqlite3_reset(stmt);
-
- ret = sqlite3_bind_text(stmt, 1, type, -1, NULL);
- if (ret != SQLITE_OK) {
- g_warning("sqlite3_bind_text() failed: %s",
- sqlite3_errmsg(sticker_db));
- return NULL;
- }
-
- ret = sqlite3_bind_text(stmt, 2, uri, -1, NULL);
- if (ret != SQLITE_OK) {
- g_warning("sqlite3_bind_text() failed: %s",
- sqlite3_errmsg(sticker_db));
- return NULL;
- }
-
- ret = sqlite3_bind_text(stmt, 3, name, -1, NULL);
- if (ret != SQLITE_OK) {
- g_warning("sqlite3_bind_text() failed: %s",
- sqlite3_errmsg(sticker_db));
- return NULL;
- }
-
- do {
- ret = sqlite3_step(stmt);
- } while (ret == SQLITE_BUSY);
-
- if (ret == SQLITE_ROW) {
- /* record found */
- value = g_strdup((const char*)sqlite3_column_text(stmt, 0));
- } else if (ret == SQLITE_DONE) {
- /* no record found */
- value = NULL;
- } else {
- /* error */
- g_warning("sqlite3_step() failed: %s",
- sqlite3_errmsg(sticker_db));
- return NULL;
- }
-
- sqlite3_reset(stmt);
- sqlite3_clear_bindings(stmt);
-
- return value;
-}
-
-static bool
-sticker_list_values(GHashTable *hash, const char *type, const char *uri)
-{
- sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_LIST];
- int ret;
- char *name, *value;
-
- assert(hash != NULL);
- assert(type != NULL);
- assert(uri != NULL);
- assert(sticker_enabled());
-
- sqlite3_reset(stmt);
-
- ret = sqlite3_bind_text(stmt, 1, type, -1, NULL);
- if (ret != SQLITE_OK) {
- g_warning("sqlite3_bind_text() failed: %s",
- sqlite3_errmsg(sticker_db));
- return false;
- }
-
- ret = sqlite3_bind_text(stmt, 2, uri, -1, NULL);
- if (ret != SQLITE_OK) {
- g_warning("sqlite3_bind_text() failed: %s",
- sqlite3_errmsg(sticker_db));
- return false;
- }
-
- do {
- ret = sqlite3_step(stmt);
- switch (ret) {
- case SQLITE_ROW:
- name = g_strdup((const char*)sqlite3_column_text(stmt, 0));
- value = g_strdup((const char*)sqlite3_column_text(stmt, 1));
- g_hash_table_insert(hash, name, value);
- break;
- case SQLITE_DONE:
- break;
- case SQLITE_BUSY:
- /* no op */
- break;
- default:
- g_warning("sqlite3_step() failed: %s",
- sqlite3_errmsg(sticker_db));
- return false;
- }
- } while (ret != SQLITE_DONE);
-
- sqlite3_reset(stmt);
- sqlite3_clear_bindings(stmt);
-
- return true;
-}
-
-static bool
-sticker_update_value(const char *type, const char *uri,
- const char *name, const char *value)
-{
- sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_UPDATE];
- int ret;
-
- assert(type != NULL);
- assert(uri != NULL);
- assert(name != NULL);
- assert(*name != 0);
- assert(value != NULL);
-
- assert(sticker_enabled());
-
- sqlite3_reset(stmt);
-
- ret = sqlite3_bind_text(stmt, 1, value, -1, NULL);
- if (ret != SQLITE_OK) {
- g_warning("sqlite3_bind_text() failed: %s",
- sqlite3_errmsg(sticker_db));
- return false;
- }
-
- ret = sqlite3_bind_text(stmt, 2, type, -1, NULL);
- if (ret != SQLITE_OK) {
- g_warning("sqlite3_bind_text() failed: %s",
- sqlite3_errmsg(sticker_db));
- return false;
- }
-
- ret = sqlite3_bind_text(stmt, 3, uri, -1, NULL);
- if (ret != SQLITE_OK) {
- g_warning("sqlite3_bind_text() failed: %s",
- sqlite3_errmsg(sticker_db));
- return false;
- }
-
- ret = sqlite3_bind_text(stmt, 4, name, -1, NULL);
- if (ret != SQLITE_OK) {
- g_warning("sqlite3_bind_text() failed: %s",
- sqlite3_errmsg(sticker_db));
- return false;
- }
-
- do {
- ret = sqlite3_step(stmt);
- } while (ret == SQLITE_BUSY);
-
- if (ret != SQLITE_DONE) {
- g_warning("sqlite3_step() failed: %s",
- sqlite3_errmsg(sticker_db));
- return false;
- }
-
- ret = sqlite3_changes(sticker_db);
-
- sqlite3_reset(stmt);
- sqlite3_clear_bindings(stmt);
-
- idle_add(IDLE_STICKER);
- return ret > 0;
-}
-
-static bool
-sticker_insert_value(const char *type, const char *uri,
- const char *name, const char *value)
-{
- sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_INSERT];
- int ret;
-
- assert(type != NULL);
- assert(uri != NULL);
- assert(name != NULL);
- assert(*name != 0);
- assert(value != NULL);
-
- assert(sticker_enabled());
-
- sqlite3_reset(stmt);
-
- ret = sqlite3_bind_text(stmt, 1, type, -1, NULL);
- if (ret != SQLITE_OK) {
- g_warning("sqlite3_bind_text() failed: %s",
- sqlite3_errmsg(sticker_db));
- return false;
- }
-
- ret = sqlite3_bind_text(stmt, 2, uri, -1, NULL);
- if (ret != SQLITE_OK) {
- g_warning("sqlite3_bind_text() failed: %s",
- sqlite3_errmsg(sticker_db));
- return false;
- }
-
- ret = sqlite3_bind_text(stmt, 3, name, -1, NULL);
- if (ret != SQLITE_OK) {
- g_warning("sqlite3_bind_text() failed: %s",
- sqlite3_errmsg(sticker_db));
- return false;
- }
-
- ret = sqlite3_bind_text(stmt, 4, value, -1, NULL);
- if (ret != SQLITE_OK) {
- g_warning("sqlite3_bind_text() failed: %s",
- sqlite3_errmsg(sticker_db));
- return false;
- }
-
- do {
- ret = sqlite3_step(stmt);
- } while (ret == SQLITE_BUSY);
-
- if (ret != SQLITE_DONE) {
- g_warning("sqlite3_step() failed: %s",
- sqlite3_errmsg(sticker_db));
- return false;
- }
-
- sqlite3_reset(stmt);
- sqlite3_clear_bindings(stmt);
-
-
- idle_add(IDLE_STICKER);
- return true;
-}
-
-bool
-sticker_store_value(const char *type, const char *uri,
- const char *name, const char *value)
-{
- assert(sticker_enabled());
- assert(type != NULL);
- assert(uri != NULL);
- assert(name != NULL);
- assert(value != NULL);
-
- if (*name == 0)
- return false;
-
- return sticker_update_value(type, uri, name, value) ||
- sticker_insert_value(type, uri, name, value);
-}
-
-bool
-sticker_delete(const char *type, const char *uri)
-{
- sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_DELETE];
- int ret;
-
- assert(sticker_enabled());
- assert(type != NULL);
- assert(uri != NULL);
-
- sqlite3_reset(stmt);
-
- ret = sqlite3_bind_text(stmt, 1, type, -1, NULL);
- if (ret != SQLITE_OK) {
- g_warning("sqlite3_bind_text() failed: %s",
- sqlite3_errmsg(sticker_db));
- return false;
- }
-
- ret = sqlite3_bind_text(stmt, 2, uri, -1, NULL);
- if (ret != SQLITE_OK) {
- g_warning("sqlite3_bind_text() failed: %s",
- sqlite3_errmsg(sticker_db));
- return false;
- }
-
- do {
- ret = sqlite3_step(stmt);
- } while (ret == SQLITE_BUSY);
-
- if (ret != SQLITE_DONE) {
- g_warning("sqlite3_step() failed: %s",
- sqlite3_errmsg(sticker_db));
- return false;
- }
-
- sqlite3_reset(stmt);
- sqlite3_clear_bindings(stmt);
-
- idle_add(IDLE_STICKER);
- return true;
-}
-
-bool
-sticker_delete_value(const char *type, const char *uri, const char *name)
-{
- sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_DELETE_VALUE];
- int ret;
-
- assert(sticker_enabled());
- assert(type != NULL);
- assert(uri != NULL);
-
- sqlite3_reset(stmt);
-
- ret = sqlite3_bind_text(stmt, 1, type, -1, NULL);
- if (ret != SQLITE_OK) {
- g_warning("sqlite3_bind_text() failed: %s",
- sqlite3_errmsg(sticker_db));
- return false;
- }
-
- ret = sqlite3_bind_text(stmt, 2, uri, -1, NULL);
- if (ret != SQLITE_OK) {
- g_warning("sqlite3_bind_text() failed: %s",
- sqlite3_errmsg(sticker_db));
- return false;
- }
-
- ret = sqlite3_bind_text(stmt, 3, name, -1, NULL);
- if (ret != SQLITE_OK) {
- g_warning("sqlite3_bind_text() failed: %s",
- sqlite3_errmsg(sticker_db));
- return false;
- }
-
- do {
- ret = sqlite3_step(stmt);
- } while (ret == SQLITE_BUSY);
-
- if (ret != SQLITE_DONE) {
- g_warning("sqlite3_step() failed: %s",
- sqlite3_errmsg(sticker_db));
- return false;
- }
-
- ret = sqlite3_changes(sticker_db);
-
- sqlite3_reset(stmt);
- sqlite3_clear_bindings(stmt);
-
- idle_add(IDLE_STICKER);
- return ret > 0;
-}
-
-static struct sticker *
-sticker_new(void)
-{
- struct sticker *sticker = g_new(struct sticker, 1);
-
- sticker->table = g_hash_table_new_full(g_str_hash, g_str_equal,
- g_free, g_free);
- return sticker;
-}
-
-void
-sticker_free(struct sticker *sticker)
-{
- assert(sticker != NULL);
- assert(sticker->table != NULL);
-
- g_hash_table_destroy(sticker->table);
- g_free(sticker);
-}
-
-const char *
-sticker_get_value(const struct sticker *sticker, const char *name)
-{
- return g_hash_table_lookup(sticker->table, name);
-}
-
-struct sticker_foreach_data {
- void (*func)(const char *name, const char *value,
- gpointer user_data);
- gpointer user_data;
-};
-
-static void
-sticker_foreach_func(gpointer key, gpointer value, gpointer user_data)
-{
- struct sticker_foreach_data *data = user_data;
-
- data->func(key, value, data->user_data);
-}
-
-void
-sticker_foreach(const struct sticker *sticker,
- void (*func)(const char *name, const char *value,
- gpointer user_data),
- gpointer user_data)
-{
- struct sticker_foreach_data data = {
- .func = func,
- .user_data = user_data,
- };
-
- g_hash_table_foreach(sticker->table, sticker_foreach_func, &data);
-}
-
-struct sticker *
-sticker_load(const char *type, const char *uri)
-{
- struct sticker *sticker = sticker_new();
- bool success;
-
- success = sticker_list_values(sticker->table, type, uri);
- if (!success) {
- sticker_free(sticker);
- return NULL;
- }
-
- if (g_hash_table_size(sticker->table) == 0) {
- /* don't return empty sticker objects */
- sticker_free(sticker);
- return NULL;
- }
-
- return sticker;
-}
-
-bool
-sticker_find(const char *type, const char *base_uri, const char *name,
- void (*func)(const char *uri, const char *value,
- gpointer user_data),
- gpointer user_data)
-{
- sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_FIND];
- int ret;
-
- assert(type != NULL);
- assert(name != NULL);
- assert(func != NULL);
- assert(sticker_enabled());
-
- sqlite3_reset(stmt);
-
- ret = sqlite3_bind_text(stmt, 1, type, -1, NULL);
- if (ret != SQLITE_OK) {
- g_warning("sqlite3_bind_text() failed: %s",
- sqlite3_errmsg(sticker_db));
- return false;
- }
-
- if (base_uri == NULL)
- base_uri = "";
-
- ret = sqlite3_bind_text(stmt, 2, base_uri, -1, NULL);
- if (ret != SQLITE_OK) {
- g_warning("sqlite3_bind_text() failed: %s",
- sqlite3_errmsg(sticker_db));
- return false;
- }
-
- ret = sqlite3_bind_text(stmt, 3, name, -1, NULL);
- if (ret != SQLITE_OK) {
- g_warning("sqlite3_bind_text() failed: %s",
- sqlite3_errmsg(sticker_db));
- return false;
- }
-
- do {
- ret = sqlite3_step(stmt);
- switch (ret) {
- case SQLITE_ROW:
- func((const char*)sqlite3_column_text(stmt, 0),
- (const char*)sqlite3_column_text(stmt, 1),
- user_data);
- break;
- case SQLITE_DONE:
- break;
- case SQLITE_BUSY:
- /* no op */
- break;
- default:
- g_warning("sqlite3_step() failed: %s",
- sqlite3_errmsg(sticker_db));
- return false;
- }
- } while (ret != SQLITE_DONE);
-
- sqlite3_reset(stmt);
- sqlite3_clear_bindings(stmt);
-
- return true;
-}
diff --git a/src/sticker.h b/src/sticker.h
deleted file mode 100644
index 5545206a5..000000000
--- a/src/sticker.h
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * This is the sticker database library. It is the backend of all the
- * sticker code in MPD.
- *
- * "Stickers" are pieces of information attached to existing MPD
- * objects (e.g. song files, directories, albums). Clients can create
- * arbitrary name/value pairs. MPD itself does not assume any special
- * meaning in them.
- *
- * The goal is to allow clients to share additional (possibly dynamic)
- * information about songs, which is neither stored on the client (not
- * available to other clients), nor stored in the song files (MPD has
- * no write access).
- *
- * Client developers should create a standard for common sticker
- * names, to ensure interoperability.
- *
- * Examples: song ratings; statistics; deferred tag writes; lyrics;
- * ...
- *
- */
-
-#ifndef STICKER_H
-#define STICKER_H
-
-#include <glib.h>
-
-#include <stdbool.h>
-
-struct sticker;
-
-/**
- * Opens the sticker database (if path is not NULL).
- *
- * @param error_r location to store the error occurring, or NULL to
- * ignore errors
- * @return true on success, false on error
- */
-bool
-sticker_global_init(const char *path, GError **error_r);
-
-/**
- * Close the sticker database.
- */
-void
-sticker_global_finish(void);
-
-/**
- * Returns true if the sticker database is configured and available.
- */
-bool
-sticker_enabled(void);
-
-/**
- * Returns one value from an object's sticker record. The caller must
- * free the return value with g_free().
- */
-char *
-sticker_load_value(const char *type, const char *uri, const char *name);
-
-/**
- * Sets a sticker value in the specified object. Overwrites existing
- * values.
- */
-bool
-sticker_store_value(const char *type, const char *uri,
- const char *name, const char *value);
-
-/**
- * Deletes a sticker from the database. All sticker values of the
- * specified object are deleted.
- */
-bool
-sticker_delete(const char *type, const char *uri);
-
-/**
- * Deletes a sticker value. Fails if no sticker with this name
- * exists.
- */
-bool
-sticker_delete_value(const char *type, const char *uri, const char *name);
-
-/**
- * Frees resources held by the sticker object.
- *
- * @param sticker the sticker object to be freed
- */
-void
-sticker_free(struct sticker *sticker);
-
-/**
- * Determines a single value in a sticker.
- *
- * @param sticker the sticker object
- * @param name the name of the sticker
- * @return the sticker value, or NULL if none was found
- */
-const char *
-sticker_get_value(const struct sticker *sticker, const char *name);
-
-/**
- * Iterates over all sticker items in a sticker.
- *
- * @param sticker the sticker object
- * @param func a callback function
- * @param user_data an opaque pointer for the callback function
- */
-void
-sticker_foreach(const struct sticker *sticker,
- void (*func)(const char *name, const char *value,
- gpointer user_data),
- gpointer user_data);
-
-/**
- * Loads the sticker for the specified resource.
- *
- * @param type the resource type, e.g. "song"
- * @param uri the URI of the resource, e.g. the song path
- * @return a sticker object, or NULL on error or if there is no sticker
- */
-struct sticker *
-sticker_load(const char *type, const char *uri);
-
-/**
- * Finds stickers with the specified name below the specified URI.
- *
- * @param type the resource type, e.g. "song"
- * @param base_uri the URI prefix of the resources, or NULL if all
- * resources should be searched
- * @param name the name of the sticker
- * @return true on success (even if no sticker was found), false on
- * failure
- */
-bool
-sticker_find(const char *type, const char *base_uri, const char *name,
- void (*func)(const char *uri, const char *value,
- gpointer user_data),
- gpointer user_data);
-
-#endif
diff --git a/src/sticker_print.c b/src/sticker_print.c
deleted file mode 100644
index 65e79513c..000000000
--- a/src/sticker_print.c
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "sticker_print.h"
-#include "sticker.h"
-#include "client.h"
-
-void
-sticker_print_value(struct client *client,
- const char *name, const char *value)
-{
- client_printf(client, "sticker: %s=%s\n", name, value);
-}
-
-static void
-print_sticker_cb(const char *name, const char *value, gpointer data)
-{
- struct client *client = data;
-
- sticker_print_value(client, name, value);
-}
-
-void
-sticker_print(struct client *client, const struct sticker *sticker)
-{
- sticker_foreach(sticker, print_sticker_cb, client);
-}
diff --git a/src/sticker_print.h b/src/sticker_print.h
deleted file mode 100644
index 7398c8083..000000000
--- a/src/sticker_print.h
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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_STICKER_PRINT_H
-#define MPD_STICKER_PRINT_H
-
-struct sticker;
-struct client;
-
-/**
- * Sends one sticker value to the client.
- */
-void
-sticker_print_value(struct client *client,
- const char *name, const char *value);
-
-/**
- * Sends all sticker values to the client.
- */
-void
-sticker_print(struct client *client, const struct sticker *sticker);
-
-#endif
diff --git a/src/stored_playlist.c b/src/stored_playlist.c
deleted file mode 100644
index 39ba2bac1..000000000
--- a/src/stored_playlist.c
+++ /dev/null
@@ -1,552 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "stored_playlist.h"
-#include "playlist_save.h"
-#include "text_file.h"
-#include "song.h"
-#include "mapper.h"
-#include "path.h"
-#include "uri.h"
-#include "database.h"
-#include "idle.h"
-#include "conf.h"
-#include "glib_compat.h"
-
-#include <assert.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <dirent.h>
-#include <string.h>
-#include <errno.h>
-
-static const char PLAYLIST_COMMENT = '#';
-
-static unsigned playlist_max_length;
-bool playlist_saveAbsolutePaths = DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS;
-
-void
-spl_global_init(void)
-{
- playlist_max_length = config_get_positive(CONF_MAX_PLAYLIST_LENGTH,
- DEFAULT_PLAYLIST_MAX_LENGTH);
-
- playlist_saveAbsolutePaths =
- config_get_bool(CONF_SAVE_ABSOLUTE_PATHS,
- DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS);
-}
-
-bool
-spl_valid_name(const char *name_utf8)
-{
- /*
- * Not supporting '/' was done out of laziness, and we should
- * really strive to support it in the future.
- *
- * Not supporting '\r' and '\n' is done out of protocol
- * limitations (and arguably laziness), but bending over head
- * over heels to modify the protocol (and compatibility with
- * all clients) to support idiots who put '\r' and '\n' in
- * filenames isn't going to happen, either.
- */
-
- return strchr(name_utf8, '/') == NULL &&
- strchr(name_utf8, '\n') == NULL &&
- strchr(name_utf8, '\r') == NULL;
-}
-
-static const char *
-spl_map(GError **error_r)
-{
- const char *path_fs = map_spl_path();
- if (path_fs == NULL)
- g_set_error_literal(error_r, playlist_quark(),
- PLAYLIST_RESULT_DISABLED,
- "Stored playlists are disabled");
-
- return path_fs;
-}
-
-static bool
-spl_check_name(const char *name_utf8, GError **error_r)
-{
- if (!spl_valid_name(name_utf8)) {
- g_set_error_literal(error_r, playlist_quark(),
- PLAYLIST_RESULT_BAD_NAME,
- "Bad playlist name");
- return false;
- }
-
- return true;
-}
-
-static char *
-spl_map_to_fs(const char *name_utf8, GError **error_r)
-{
- if (spl_map(error_r) == NULL ||
- !spl_check_name(name_utf8, error_r))
- return NULL;
-
- char *path_fs = map_spl_utf8_to_fs(name_utf8);
- if (path_fs == NULL)
- g_set_error_literal(error_r, playlist_quark(),
- PLAYLIST_RESULT_BAD_NAME,
- "Bad playlist name");
-
- return path_fs;
-}
-
-/**
- * Create a GError for the current errno.
- */
-static void
-playlist_errno(GError **error_r)
-{
- switch (errno) {
- case ENOENT:
- g_set_error_literal(error_r, playlist_quark(),
- PLAYLIST_RESULT_NO_SUCH_LIST,
- "No such playlist");
- break;
-
- default:
- g_set_error_literal(error_r, g_file_error_quark(), errno,
- g_strerror(errno));
- break;
- }
-}
-
-static struct stored_playlist_info *
-load_playlist_info(const char *parent_path_fs, const char *name_fs)
-{
- size_t name_length = strlen(name_fs);
- char *path_fs, *name, *name_utf8;
- int ret;
- struct stat st;
- struct stored_playlist_info *playlist;
-
- if (name_length < sizeof(PLAYLIST_FILE_SUFFIX) ||
- memchr(name_fs, '\n', name_length) != NULL)
- return NULL;
-
- if (!g_str_has_suffix(name_fs, PLAYLIST_FILE_SUFFIX))
- return NULL;
-
- path_fs = g_build_filename(parent_path_fs, name_fs, NULL);
- ret = stat(path_fs, &st);
- g_free(path_fs);
- if (ret < 0 || !S_ISREG(st.st_mode))
- return NULL;
-
- name = g_strndup(name_fs,
- name_length + 1 - sizeof(PLAYLIST_FILE_SUFFIX));
- name_utf8 = fs_charset_to_utf8(name);
- g_free(name);
- if (name_utf8 == NULL)
- return NULL;
-
- playlist = g_new(struct stored_playlist_info, 1);
- playlist->name = name_utf8;
- playlist->mtime = st.st_mtime;
- return playlist;
-}
-
-GPtrArray *
-spl_list(GError **error_r)
-{
- const char *parent_path_fs = spl_map(error_r);
- DIR *dir;
- struct dirent *ent;
- GPtrArray *list;
- struct stored_playlist_info *playlist;
-
- if (parent_path_fs == NULL)
- return NULL;
-
- dir = opendir(parent_path_fs);
- if (dir == NULL) {
- g_set_error_literal(error_r, g_file_error_quark(), errno,
- g_strerror(errno));
- return NULL;
- }
-
- list = g_ptr_array_new();
-
- while ((ent = readdir(dir)) != NULL) {
- playlist = load_playlist_info(parent_path_fs, ent->d_name);
- if (playlist != NULL)
- g_ptr_array_add(list, playlist);
- }
-
- closedir(dir);
- return list;
-}
-
-void
-spl_list_free(GPtrArray *list)
-{
- for (unsigned i = 0; i < list->len; ++i) {
- struct stored_playlist_info *playlist =
- g_ptr_array_index(list, i);
- g_free(playlist->name);
- g_free(playlist);
- }
-
- g_ptr_array_free(list, true);
-}
-
-static bool
-spl_save(GPtrArray *list, const char *utf8path, GError **error_r)
-{
- FILE *file;
-
- assert(utf8path != NULL);
-
- if (spl_map(error_r) == NULL)
- return false;
-
- char *path_fs = spl_map_to_fs(utf8path, error_r);
- if (path_fs == NULL)
- return false;
-
- file = fopen(path_fs, "w");
- g_free(path_fs);
- if (file == NULL) {
- playlist_errno(error_r);
- return false;
- }
-
- for (unsigned i = 0; i < list->len; ++i) {
- const char *uri = g_ptr_array_index(list, i);
- playlist_print_uri(file, uri);
- }
-
- fclose(file);
- return true;
-}
-
-GPtrArray *
-spl_load(const char *utf8path, GError **error_r)
-{
- FILE *file;
- GPtrArray *list;
- char *path_fs;
-
- if (spl_map(error_r) == NULL)
- return NULL;
-
- path_fs = spl_map_to_fs(utf8path, error_r);
- if (path_fs == NULL)
- return NULL;
-
- file = fopen(path_fs, "r");
- g_free(path_fs);
- if (file == NULL) {
- playlist_errno(error_r);
- return NULL;
- }
-
- list = g_ptr_array_new();
-
- GString *buffer = g_string_sized_new(1024);
- char *s;
- while ((s = read_text_line(file, buffer)) != NULL) {
- if (*s == 0 || *s == PLAYLIST_COMMENT)
- continue;
-
- if (!uri_has_scheme(s)) {
- char *path_utf8;
-
- path_utf8 = map_fs_to_utf8(s);
- if (path_utf8 == NULL)
- continue;
-
- s = path_utf8;
- } else
- s = g_strdup(s);
-
- g_ptr_array_add(list, s);
-
- if (list->len >= playlist_max_length)
- break;
- }
-
- fclose(file);
- return list;
-}
-
-void
-spl_free(GPtrArray *list)
-{
- for (unsigned i = 0; i < list->len; ++i) {
- char *uri = g_ptr_array_index(list, i);
- g_free(uri);
- }
-
- g_ptr_array_free(list, true);
-}
-
-static char *
-spl_remove_index_internal(GPtrArray *list, unsigned idx)
-{
- char *uri;
-
- assert(idx < list->len);
-
- uri = g_ptr_array_remove_index(list, idx);
- assert(uri != NULL);
- return uri;
-}
-
-static void
-spl_insert_index_internal(GPtrArray *list, unsigned idx, char *uri)
-{
- assert(idx <= list->len);
-
- g_ptr_array_add(list, uri);
-
- memmove(list->pdata + idx + 1, list->pdata + idx,
- (list->len - idx - 1) * sizeof(list->pdata[0]));
- g_ptr_array_index(list, idx) = uri;
-}
-
-bool
-spl_move_index(const char *utf8path, unsigned src, unsigned dest,
- GError **error_r)
-{
- char *uri;
-
- if (src == dest)
- /* this doesn't check whether the playlist exists, but
- what the hell.. */
- return true;
-
- GPtrArray *list = spl_load(utf8path, error_r);
- if (list == NULL)
- return false;
-
- if (src >= list->len || dest >= list->len) {
- spl_free(list);
- g_set_error_literal(error_r, playlist_quark(),
- PLAYLIST_RESULT_BAD_RANGE,
- "Bad range");
- return false;
- }
-
- uri = spl_remove_index_internal(list, src);
- spl_insert_index_internal(list, dest, uri);
-
- bool result = spl_save(list, utf8path, error_r);
-
- spl_free(list);
-
- idle_add(IDLE_STORED_PLAYLIST);
- return result;
-}
-
-bool
-spl_clear(const char *utf8path, GError **error_r)
-{
- FILE *file;
-
- if (spl_map(error_r) == NULL)
- return false;
-
- char *path_fs = spl_map_to_fs(utf8path, error_r);
- if (path_fs == NULL)
- return false;
-
- file = fopen(path_fs, "w");
- g_free(path_fs);
- if (file == NULL) {
- playlist_errno(error_r);
- return false;
- }
-
- fclose(file);
-
- idle_add(IDLE_STORED_PLAYLIST);
- return true;
-}
-
-bool
-spl_delete(const char *name_utf8, GError **error_r)
-{
- char *path_fs;
- int ret;
-
- path_fs = spl_map_to_fs(name_utf8, error_r);
- if (path_fs == NULL)
- return false;
-
- ret = unlink(path_fs);
- g_free(path_fs);
- if (ret < 0) {
- playlist_errno(error_r);
- return false;
- }
-
- idle_add(IDLE_STORED_PLAYLIST);
- return true;
-}
-
-bool
-spl_remove_index(const char *utf8path, unsigned pos, GError **error_r)
-{
- char *uri;
-
- GPtrArray *list = spl_load(utf8path, error_r);
- if (list == NULL)
- return false;
-
- if (pos >= list->len) {
- spl_free(list);
- g_set_error_literal(error_r, playlist_quark(),
- PLAYLIST_RESULT_BAD_RANGE,
- "Bad range");
- return false;
- }
-
- uri = spl_remove_index_internal(list, pos);
- g_free(uri);
- bool result = spl_save(list, utf8path, error_r);
-
- spl_free(list);
-
- idle_add(IDLE_STORED_PLAYLIST);
- return result;
-}
-
-bool
-spl_append_song(const char *utf8path, struct song *song, GError **error_r)
-{
- FILE *file;
- struct stat st;
-
- if (spl_map(error_r) == NULL)
- return false;
-
- char *path_fs = spl_map_to_fs(utf8path, error_r);
- if (path_fs == NULL)
- return false;
-
- file = fopen(path_fs, "a");
- g_free(path_fs);
- if (file == NULL) {
- playlist_errno(error_r);
- return false;
- }
-
- if (fstat(fileno(file), &st) < 0) {
- playlist_errno(error_r);
- fclose(file);
- return false;
- }
-
- if (st.st_size / (MPD_PATH_MAX + 1) >= (off_t)playlist_max_length) {
- fclose(file);
- g_set_error_literal(error_r, playlist_quark(),
- PLAYLIST_RESULT_TOO_LARGE,
- "Stored playlist is too large");
- return false;
- }
-
- playlist_print_song(file, song);
-
- fclose(file);
-
- idle_add(IDLE_STORED_PLAYLIST);
- return true;
-}
-
-bool
-spl_append_uri(const char *url, const char *utf8file, GError **error_r)
-{
- struct song *song;
-
- if (uri_has_scheme(url)) {
- song = song_remote_new(url);
- bool success = spl_append_song(utf8file, song, error_r);
- song_free(song);
- return success;
- } else {
- song = db_get_song(url);
- if (song == NULL) {
- g_set_error_literal(error_r, playlist_quark(),
- PLAYLIST_RESULT_NO_SUCH_SONG,
- "No such song");
- return false;
- }
-
- return spl_append_song(utf8file, song, error_r);
- }
-}
-
-static bool
-spl_rename_internal(const char *from_path_fs, const char *to_path_fs,
- GError **error_r)
-{
- if (!g_file_test(from_path_fs, G_FILE_TEST_IS_REGULAR)) {
- g_set_error_literal(error_r, playlist_quark(),
- PLAYLIST_RESULT_NO_SUCH_LIST,
- "No such playlist");
- return false;
- }
-
- if (g_file_test(to_path_fs, G_FILE_TEST_EXISTS)) {
- g_set_error_literal(error_r, playlist_quark(),
- PLAYLIST_RESULT_LIST_EXISTS,
- "Playlist exists already");
- return false;
- }
-
- if (rename(from_path_fs, to_path_fs) < 0) {
- playlist_errno(error_r);
- return false;
- }
-
- idle_add(IDLE_STORED_PLAYLIST);
- return true;
-}
-
-bool
-spl_rename(const char *utf8from, const char *utf8to, GError **error_r)
-{
- if (spl_map(error_r) == NULL)
- return false;
-
- char *from_path_fs = spl_map_to_fs(utf8from, error_r);
- if (from_path_fs == NULL)
- return false;
-
- char *to_path_fs = spl_map_to_fs(utf8to, error_r);
- if (to_path_fs == NULL) {
- g_free(from_path_fs);
- return false;
- }
-
- bool success = spl_rename_internal(from_path_fs, to_path_fs, error_r);
-
- g_free(from_path_fs);
- g_free(to_path_fs);
-
- return success;
-}
diff --git a/src/stored_playlist.h b/src/stored_playlist.h
deleted file mode 100644
index cfe49633c..000000000
--- a/src/stored_playlist.h
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * 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_STORED_PLAYLIST_H
-#define MPD_STORED_PLAYLIST_H
-
-#include <glib.h>
-#include <stdbool.h>
-#include <time.h>
-
-struct song;
-
-struct stored_playlist_info {
- char *name;
-
- time_t mtime;
-};
-
-extern bool playlist_saveAbsolutePaths;
-
-/**
- * Perform some global initialization, e.g. load configuration values.
- */
-void
-spl_global_init(void);
-
-/**
- * Determines whether the specified string is a valid name for a
- * stored playlist.
- */
-bool
-spl_valid_name(const char *name_utf8);
-
-/**
- * Returns a list of stored_playlist_info struct pointers. Returns
- * NULL if an error occurred.
- */
-GPtrArray *
-spl_list(GError **error_r);
-
-void
-spl_list_free(GPtrArray *list);
-
-GPtrArray *
-spl_load(const char *utf8path, GError **error_r);
-
-void
-spl_free(GPtrArray *list);
-
-bool
-spl_move_index(const char *utf8path, unsigned src, unsigned dest,
- GError **error_r);
-
-bool
-spl_clear(const char *utf8path, GError **error_r);
-
-bool
-spl_delete(const char *name_utf8, GError **error_r);
-
-bool
-spl_remove_index(const char *utf8path, unsigned pos, GError **error_r);
-
-bool
-spl_append_song(const char *utf8path, struct song *song, GError **error_r);
-
-bool
-spl_append_uri(const char *file, const char *utf8file, GError **error_r);
-
-bool
-spl_rename(const char *utf8from, const char *utf8to, GError **error_r);
-
-#endif
diff --git a/src/string_util.c b/src/string_util.c
deleted file mode 100644
index 6e5429076..000000000
--- a/src/string_util.c
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "string_util.h"
-
-#include <glib.h>
-
-#include <assert.h>
-
-const char *
-strchug_fast_c(const char *p)
-{
- while (*p != 0 && g_ascii_isspace(*p))
- ++p;
-
- return p;
-}
-
-bool
-string_array_contains(const char *const* haystack, const char *needle)
-{
- assert(haystack != NULL);
- assert(needle != NULL);
-
- for (; *haystack != NULL; ++haystack)
- if (g_ascii_strcasecmp(*haystack, needle) == 0)
- return true;
-
- return false;
-}
diff --git a/src/string_util.h b/src/string_util.h
deleted file mode 100644
index dc80a46ef..000000000
--- a/src/string_util.h
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * 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_STRING_UTIL_H
-#define MPD_STRING_UTIL_H
-
-#include <glib.h>
-
-#include <stdbool.h>
-
-/**
- * Remove the "const" attribute from a string pointer. This is a
- * dirty hack, don't use it unless you know what you're doing!
- */
-G_GNUC_CONST
-static inline char *
-deconst_string(const char *p)
-{
- union {
- const char *in;
- char *out;
- } u = {
- .in = p,
- };
-
- return u.out;
-}
-
-/**
- * Returns a pointer to the first non-whitespace character in the
- * string, or to the end of the string.
- *
- * This is a faster version of g_strchug(), because it does not move
- * data.
- */
-G_GNUC_PURE
-const char *
-strchug_fast_c(const char *p);
-
-/**
- * Same as strchug_fast_c(), but works with a writable pointer.
- */
-G_GNUC_PURE
-static inline char *
-strchug_fast(char *p)
-{
- return deconst_string(strchug_fast_c(p));
-}
-
-/**
- * Checks whether a string array contains the specified string.
- *
- * @param haystack a NULL terminated list of strings
- * @param needle the string to search for; the comparison is
- * case-insensitive for ASCII characters
- * @return true if found
- */
-bool
-string_array_contains(const char *const* haystack, const char *needle);
-
-#endif
diff --git a/src/strset.c b/src/strset.c
deleted file mode 100644
index 5862e4075..000000000
--- a/src/strset.c
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "strset.h"
-
-#include <assert.h>
-#include <string.h>
-#include <stdlib.h>
-
-#define NUM_SLOTS 16384
-
-struct strset_slot {
- struct strset_slot *next;
- const char *value;
-};
-
-struct strset {
- unsigned size;
-
- struct strset_slot *current_slot;
- unsigned next_slot;
-
- struct strset_slot slots[NUM_SLOTS];
-};
-
-static unsigned calc_hash(const char *p) {
- unsigned hash = 5381;
-
- assert(p != NULL);
-
- while (*p != 0)
- hash = (hash << 5) + hash + *p++;
-
- return hash;
-}
-
-G_GNUC_MALLOC struct strset *strset_new(void)
-{
- struct strset *set = g_new0(struct strset, 1);
- return set;
-}
-
-void strset_free(struct strset *set)
-{
- unsigned i;
-
- for (i = 0; i < NUM_SLOTS; ++i) {
- struct strset_slot *slot = set->slots[i].next, *next;
-
- while (slot != NULL) {
- next = slot->next;
- g_free(slot);
- slot = next;
- }
- }
-
- g_free(set);
-}
-
-void strset_add(struct strset *set, const char *value)
-{
- struct strset_slot *base_slot
- = &set->slots[calc_hash(value) % NUM_SLOTS];
- struct strset_slot *slot = base_slot;
-
- if (base_slot->value == NULL) {
- /* empty slot - put into base_slot */
- assert(base_slot->next == NULL);
-
- base_slot->value = value;
- ++set->size;
- return;
- }
-
- for (slot = base_slot; slot != NULL; slot = slot->next)
- if (strcmp(slot->value, value) == 0)
- /* found it - do nothing */
- return;
-
- /* insert it into the slot chain */
- slot = g_new(struct strset_slot, 1);
- slot->next = base_slot->next;
- slot->value = value;
- base_slot->next = slot;
- ++set->size;
-}
-
-int strset_get(const struct strset *set, const char *value)
-{
- const struct strset_slot *slot = &set->slots[calc_hash(value)];
-
- if (slot->value == NULL)
- return 0;
-
- for (slot = slot->next; slot != NULL; slot = slot->next)
- if (strcmp(slot->value, value) == 0)
- /* found it - do nothing */
- return 1;
-
- return 0;
-}
-
-unsigned strset_size(const struct strset *set)
-{
- return set->size;
-}
-
-void strset_rewind(struct strset *set)
-{
- set->current_slot = NULL;
- set->next_slot = 0;
-}
-
-const char *strset_next(struct strset *set)
-{
- if (set->current_slot != NULL && set->current_slot->next != NULL) {
- set->current_slot = set->current_slot->next;
- return set->current_slot->value;
- }
-
- while (set->next_slot < NUM_SLOTS &&
- set->slots[set->next_slot].value == NULL)
- ++set->next_slot;
-
- if (set->next_slot >= NUM_SLOTS)
- return NULL;
-
- set->current_slot = &set->slots[set->next_slot++];
- return set->current_slot->value;
-}
-
diff --git a/src/strset.h b/src/strset.h
deleted file mode 100644
index 5382e59b8..000000000
--- a/src/strset.h
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * 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.
- */
-
-/**
- * "struct strset" is a hashed string set: you can add strings to this
- * library, and it stores them as a set of unique strings. You can
- * get the size of the set, and you can enumerate through all values.
- *
- * It is important to note that the strset does not copy the string
- * values - it stores the exact pointers it was given in strset_add().
- */
-
-#ifndef MPD_STRSET_H
-#define MPD_STRSET_H
-
-#include <glib.h>
-
-struct strset;
-
-G_GNUC_MALLOC struct strset *strset_new(void);
-
-void strset_free(struct strset *set);
-
-void strset_add(struct strset *set, const char *value);
-
-int strset_get(const struct strset *set, const char *value);
-
-unsigned strset_size(const struct strset *set);
-
-void strset_rewind(struct strset *set);
-
-const char *strset_next(struct strset *set);
-
-#endif
diff --git a/src/tag.c b/src/tag.c
deleted file mode 100644
index c0faa7ab2..000000000
--- a/src/tag.c
+++ /dev/null
@@ -1,523 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "tag.h"
-#include "tag_internal.h"
-#include "tag_pool.h"
-#include "conf.h"
-#include "song.h"
-#include "mpd_error.h"
-
-#include <glib.h>
-#include <assert.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-/**
- * Maximum number of items managed in the bulk list; if it is
- * exceeded, we switch back to "normal" reallocation.
- */
-#define BULK_MAX 64
-
-static struct {
-#ifndef NDEBUG
- bool busy;
-#endif
- struct tag_item *items[BULK_MAX];
-} bulk;
-
-const char *tag_item_names[TAG_NUM_OF_ITEM_TYPES] = {
- [TAG_ARTIST] = "Artist",
- [TAG_ARTIST_SORT] = "ArtistSort",
- [TAG_ALBUM] = "Album",
- [TAG_ALBUM_ARTIST] = "AlbumArtist",
- [TAG_ALBUM_ARTIST_SORT] = "AlbumArtistSort",
- [TAG_TITLE] = "Title",
- [TAG_TRACK] = "Track",
- [TAG_NAME] = "Name",
- [TAG_GENRE] = "Genre",
- [TAG_DATE] = "Date",
- [TAG_COMPOSER] = "Composer",
- [TAG_PERFORMER] = "Performer",
- [TAG_COMMENT] = "Comment",
- [TAG_DISC] = "Disc",
-
- /* MusicBrainz tags from http://musicbrainz.org/doc/MusicBrainzTag */
- [TAG_MUSICBRAINZ_ARTISTID] = "MUSICBRAINZ_ARTISTID",
- [TAG_MUSICBRAINZ_ALBUMID] = "MUSICBRAINZ_ALBUMID",
- [TAG_MUSICBRAINZ_ALBUMARTISTID] = "MUSICBRAINZ_ALBUMARTISTID",
- [TAG_MUSICBRAINZ_TRACKID] = "MUSICBRAINZ_TRACKID",
-};
-
-bool ignore_tag_items[TAG_NUM_OF_ITEM_TYPES];
-
-enum tag_type
-tag_name_parse(const char *name)
-{
- assert(name != NULL);
-
- for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) {
- assert(tag_item_names[i] != NULL);
-
- if (strcmp(name, tag_item_names[i]) == 0)
- return (enum tag_type)i;
- }
-
- return TAG_NUM_OF_ITEM_TYPES;
-}
-
-enum tag_type
-tag_name_parse_i(const char *name)
-{
- assert(name != NULL);
-
- for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) {
- assert(tag_item_names[i] != NULL);
-
- if (g_ascii_strcasecmp(name, tag_item_names[i]) == 0)
- return (enum tag_type)i;
- }
-
- return TAG_NUM_OF_ITEM_TYPES;
-}
-
-static size_t items_size(const struct tag *tag)
-{
- return tag->num_items * sizeof(struct tag_item *);
-}
-
-void tag_lib_init(void)
-{
- const char *value;
- int quit = 0;
- char *temp;
- char *s;
- char *c;
- enum tag_type type;
-
- /* parse the "metadata_to_use" config parameter below */
-
- /* ignore comments by default */
- ignore_tag_items[TAG_COMMENT] = true;
-
- value = config_get_string(CONF_METADATA_TO_USE, NULL);
- if (value == NULL)
- return;
-
- memset(ignore_tag_items, true, TAG_NUM_OF_ITEM_TYPES);
-
- if (0 == g_ascii_strcasecmp(value, "none"))
- return;
-
- temp = c = s = g_strdup(value);
- while (!quit) {
- if (*s == ',' || *s == '\0') {
- if (*s == '\0')
- quit = 1;
- *s = '\0';
-
- c = g_strstrip(c);
- if (*c == 0)
- continue;
-
- type = tag_name_parse_i(c);
- if (type == TAG_NUM_OF_ITEM_TYPES)
- MPD_ERROR("error parsing metadata item \"%s\"",
- c);
-
- ignore_tag_items[type] = false;
-
- s++;
- c = s;
- }
- s++;
- }
-
- g_free(temp);
-}
-
-struct tag *tag_new(void)
-{
- struct tag *ret = g_new(struct tag, 1);
- ret->items = NULL;
- ret->time = -1;
- ret->has_playlist = false;
- ret->num_items = 0;
- return ret;
-}
-
-static void tag_delete_item(struct tag *tag, unsigned idx)
-{
- assert(idx < tag->num_items);
- tag->num_items--;
-
- g_mutex_lock(tag_pool_lock);
- tag_pool_put_item(tag->items[idx]);
- g_mutex_unlock(tag_pool_lock);
-
- if (tag->num_items - idx > 0) {
- memmove(tag->items + idx, tag->items + idx + 1,
- (tag->num_items - idx) * sizeof(tag->items[0]));
- }
-
- if (tag->num_items > 0) {
- tag->items = g_realloc(tag->items, items_size(tag));
- } else {
- g_free(tag->items);
- tag->items = NULL;
- }
-}
-
-void tag_clear_items_by_type(struct tag *tag, enum tag_type type)
-{
- for (unsigned i = 0; i < tag->num_items; i++) {
- if (tag->items[i]->type == type) {
- tag_delete_item(tag, i);
- /* decrement since when just deleted this node */
- i--;
- }
- }
-}
-
-void tag_free(struct tag *tag)
-{
- int i;
-
- assert(tag != NULL);
-
- g_mutex_lock(tag_pool_lock);
- for (i = tag->num_items; --i >= 0; )
- tag_pool_put_item(tag->items[i]);
- g_mutex_unlock(tag_pool_lock);
-
- if (tag->items == bulk.items) {
-#ifndef NDEBUG
- assert(bulk.busy);
- bulk.busy = false;
-#endif
- } else
- g_free(tag->items);
-
- g_free(tag);
-}
-
-struct tag *tag_dup(const struct tag *tag)
-{
- struct tag *ret;
-
- if (!tag)
- return NULL;
-
- ret = tag_new();
- ret->time = tag->time;
- ret->has_playlist = tag->has_playlist;
- ret->num_items = tag->num_items;
- ret->items = ret->num_items > 0 ? g_malloc(items_size(tag)) : NULL;
-
- g_mutex_lock(tag_pool_lock);
- for (unsigned i = 0; i < tag->num_items; i++)
- ret->items[i] = tag_pool_dup_item(tag->items[i]);
- g_mutex_unlock(tag_pool_lock);
-
- return ret;
-}
-
-struct tag *
-tag_merge(const struct tag *base, const struct tag *add)
-{
- struct tag *ret;
- unsigned n;
-
- assert(base != NULL);
- assert(add != NULL);
-
- /* allocate new tag object */
-
- ret = tag_new();
- ret->time = add->time > 0 ? add->time : base->time;
- ret->num_items = base->num_items + add->num_items;
- ret->items = ret->num_items > 0 ? g_malloc(items_size(ret)) : NULL;
-
- g_mutex_lock(tag_pool_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 (!tag_has_type(add, base->items[i]->type))
- ret->items[n++] = tag_pool_dup_item(base->items[i]);
-
- g_mutex_unlock(tag_pool_lock);
-
- 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 = g_realloc(ret->items, items_size(ret));
- }
-
- return ret;
-}
-
-struct tag *
-tag_merge_replace(struct tag *base, struct tag *add)
-{
- if (add == NULL)
- return base;
-
- if (base == NULL)
- return add;
-
- struct tag *tag = tag_merge(base, add);
- tag_free(base);
- tag_free(add);
-
- return tag;
-}
-
-const char *
-tag_get_value(const struct tag *tag, enum tag_type type)
-{
- assert(tag != NULL);
- assert(type < TAG_NUM_OF_ITEM_TYPES);
-
- for (unsigned i = 0; i < tag->num_items; i++)
- if (tag->items[i]->type == type)
- return tag->items[i]->value;
-
- return NULL;
-}
-
-bool tag_has_type(const struct tag *tag, enum tag_type type)
-{
- return tag_get_value(tag, type) != NULL;
-}
-
-bool tag_equal(const struct tag *tag1, const struct tag *tag2)
-{
- if (tag1 == NULL && tag2 == NULL)
- return true;
- else if (!tag1 || !tag2)
- return false;
-
- if (tag1->time != tag2->time)
- return false;
-
- if (tag1->num_items != tag2->num_items)
- return false;
-
- for (unsigned i = 0; i < tag1->num_items; i++) {
- if (tag1->items[i]->type != tag2->items[i]->type)
- return false;
- if (strcmp(tag1->items[i]->value, tag2->items[i]->value)) {
- return false;
- }
- }
-
- return true;
-}
-
-/**
- * Replace invalid sequences with the question mark.
- */
-static char *
-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);
-
- do {
- dest[end - src] = '?';
- } while (!g_utf8_validate(end + 1, (src + length) - (end + 1), &end));
-
- return dest;
-}
-
-static char *
-fix_utf8(const char *str, size_t length)
-{
- const gchar *end;
- char *temp;
- gsize written;
-
- assert(str != NULL);
-
- /* check if the string is already valid UTF-8 */
- if (g_utf8_validate(str, length, &end))
- return NULL;
-
- /* no, it's not - try to import it from ISO-Latin-1 */
- temp = g_convert(str, length, "utf-8", "iso-8859-1",
- NULL, &written, NULL);
- if (temp != NULL)
- /* success! */
- return temp;
-
- /* no, still broken - there's no medication, just patch
- invalid sequences */
- return patch_utf8(str, length, end);
-}
-
-void tag_begin_add(struct tag *tag)
-{
- assert(!bulk.busy);
- assert(tag != NULL);
- assert(tag->items == NULL);
- assert(tag->num_items == 0);
-
-#ifndef NDEBUG
- bulk.busy = true;
-#endif
- tag->items = bulk.items;
-}
-
-void tag_end_add(struct tag *tag)
-{
- if (tag->items == bulk.items) {
- assert(tag->num_items <= BULK_MAX);
-
- if (tag->num_items > 0) {
- /* copy the tag items from the bulk list over
- to a new list (which fits exactly) */
- tag->items = g_malloc(items_size(tag));
- memcpy(tag->items, bulk.items, items_size(tag));
- } else
- tag->items = NULL;
- }
-
-#ifndef NDEBUG
- bulk.busy = false;
-#endif
-}
-
-static bool
-char_is_non_printable(unsigned char ch)
-{
- return ch < 0x20;
-}
-
-static const char *
-find_non_printable(const char *p, size_t length)
-{
- for (size_t i = 0; i < length; ++i)
- if (char_is_non_printable(p[i]))
- return p + i;
-
- return NULL;
-}
-
-/**
- * Clears all non-printable characters, convert them to space.
- * Returns NULL if nothing needs to be cleared.
- */
-static char *
-clear_non_printable(const char *p, size_t length)
-{
- const char *first = find_non_printable(p, length);
- char *dest;
-
- if (first == NULL)
- return NULL;
-
- dest = g_strndup(p, length);
-
- for (size_t i = first - p; i < length; ++i)
- if (char_is_non_printable(dest[i]))
- dest[i] = ' ';
-
- return dest;
-}
-
-static char *
-fix_tag_value(const char *p, size_t length)
-{
- char *utf8, *cleared;
-
- utf8 = fix_utf8(p, length);
- if (utf8 != NULL) {
- p = utf8;
- length = strlen(p);
- }
-
- cleared = clear_non_printable(p, length);
- if (cleared == NULL)
- cleared = utf8;
- else
- g_free(utf8);
-
- return cleared;
-}
-
-static void
-tag_add_item_internal(struct tag *tag, enum tag_type type,
- const char *value, size_t len)
-{
- unsigned int i = tag->num_items;
- char *p;
-
- p = fix_tag_value(value, len);
- if (p != NULL) {
- value = p;
- len = strlen(value);
- }
-
- tag->num_items++;
-
- if (tag->items != bulk.items)
- /* bulk mode disabled */
- tag->items = g_realloc(tag->items, items_size(tag));
- else if (tag->num_items >= BULK_MAX) {
- /* bulk list already full - switch back to non-bulk */
- assert(bulk.busy);
-
- tag->items = g_malloc(items_size(tag));
- memcpy(tag->items, bulk.items,
- items_size(tag) - sizeof(struct tag_item *));
- }
-
- g_mutex_lock(tag_pool_lock);
- tag->items[i] = tag_pool_get_item(type, value, len);
- g_mutex_unlock(tag_pool_lock);
-
- g_free(p);
-}
-
-void tag_add_item_n(struct tag *tag, enum tag_type type,
- const char *value, size_t len)
-{
- if (ignore_tag_items[type])
- {
- return;
- }
- if (!value || !len)
- return;
-
- tag_add_item_internal(tag, type, value, len);
-}
diff --git a/src/tag.h b/src/tag.h
deleted file mode 100644
index 2556cf3a7..000000000
--- a/src/tag.h
+++ /dev/null
@@ -1,239 +0,0 @@
-/*
- * 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_TAG_H
-#define MPD_TAG_H
-
-#include "gcc.h"
-
-#include <stdint.h>
-#include <stddef.h>
-#include <stdbool.h>
-#include <string.h>
-
-/**
- * Codes for the type of a tag item.
- */
-enum tag_type {
- TAG_ARTIST,
- TAG_ARTIST_SORT,
- TAG_ALBUM,
- TAG_ALBUM_ARTIST,
- TAG_ALBUM_ARTIST_SORT,
- TAG_TITLE,
- TAG_TRACK,
- TAG_NAME,
- TAG_GENRE,
- TAG_DATE,
- TAG_COMPOSER,
- TAG_PERFORMER,
- TAG_COMMENT,
- TAG_DISC,
-
- TAG_MUSICBRAINZ_ARTISTID,
- TAG_MUSICBRAINZ_ALBUMID,
- TAG_MUSICBRAINZ_ALBUMARTISTID,
- TAG_MUSICBRAINZ_TRACKID,
-
- TAG_NUM_OF_ITEM_TYPES
-};
-
-/**
- * An array of strings, which map the #tag_type to its machine
- * readable name (specific to the MPD protocol).
- */
-extern const char *tag_item_names[];
-
-/**
- * One tag value. It is a mapping of #tag_type to am arbitrary string
- * value. Each tag can have multiple items of one tag type (although
- * few clients support that).
- */
-struct tag_item {
- /** the type of this item */
- enum tag_type type;
-
- /**
- * the value of this tag; this is a variable length string
- */
- char value[sizeof(long)];
-} gcc_packed;
-
-/**
- * The meta information about a song file. It is a MPD specific
- * subset of tags (e.g. from ID3, vorbis comments, ...).
- */
-struct tag {
- /**
- * The duration of the song (in seconds). A value of zero
- * means that the length is unknown. If the duration is
- * really between zero and one second, you should round up to
- * 1.
- */
- int time;
-
- /**
- * Does this file have an embedded playlist (e.g. embedded CUE
- * sheet)?
- */
- bool has_playlist;
-
- /** an array of tag items */
- struct tag_item **items;
-
- /** the total number of tag items in the #items array */
- unsigned num_items;
-};
-
-/**
- * Parse the string, and convert it into a #tag_type. Returns
- * #TAG_NUM_OF_ITEM_TYPES if the string could not be recognized.
- */
-enum tag_type
-tag_name_parse(const char *name);
-
-/**
- * Parse the string, and convert it into a #tag_type. Returns
- * #TAG_NUM_OF_ITEM_TYPES if the string could not be recognized.
- *
- * Case does not matter.
- */
-enum tag_type
-tag_name_parse_i(const char *name);
-
-/**
- * Creates an empty #tag.
- */
-struct tag *tag_new(void);
-
-/**
- * Initializes the tag library.
- */
-void tag_lib_init(void);
-
-/**
- * Clear all tag items with the specified type.
- */
-void tag_clear_items_by_type(struct tag *tag, enum tag_type type);
-
-/**
- * Frees a #tag object and all its items.
- */
-void tag_free(struct tag *tag);
-
-/**
- * Gives an optional hint to the tag library that we will now add
- * several tag items; this is used by the library to optimize memory
- * allocation. Only one tag may be in this state, and this tag must
- * not have any items yet. You must call tag_end_add() when you are
- * done.
- */
-void tag_begin_add(struct tag *tag);
-
-/**
- * Finishes the operation started with tag_begin_add().
- */
-void tag_end_add(struct tag *tag);
-
-/**
- * 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 (not null-terminated)
- * @param len the length of #value
- */
-void tag_add_item_n(struct tag *tag, enum tag_type 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)
- */
-static inline void
-tag_add_item(struct tag *tag, enum tag_type type, const char *value)
-{
- tag_add_item_n(tag, type, value, strlen(value));
-}
-
-/**
- * Duplicates a #tag object.
- */
-struct tag *tag_dup(const struct tag *tag);
-
-/**
- * Merges the data from two tags. If both tags share data for the
- * same tag_type, only data from "add" is used.
- *
- * @return a newly allocated tag, which must be freed with tag_free()
- */
-struct tag *
-tag_merge(const struct tag *base, const struct tag *add);
-
-/**
- * Merges the data from two tags. Any of the two may be NULL. Both
- * are freed by this function.
- *
- * @return a newly allocated tag, which must be freed with tag_free()
- */
-struct tag *
-tag_merge_replace(struct tag *base, struct tag *add);
-
-/**
- * Returns true if the tag contains no items. This ignores the "time"
- * attribute.
- */
-static inline bool
-tag_is_empty(const struct tag *tag)
-{
- return tag->num_items == 0;
-}
-
-/**
- * Returns true if the tag contains any information.
- */
-static inline bool
-tag_is_defined(const struct tag *tag)
-{
- return !tag_is_empty(tag) || tag->time >= 0;
-}
-
-/**
- * Returns the first value of the specified tag type, or NULL if none
- * is present in this tag object.
- */
-const char *
-tag_get_value(const struct tag *tag, enum tag_type type);
-
-/**
- * Checks whether the tag contains one or more items with
- * the specified type.
- */
-bool tag_has_type(const struct tag *tag, enum tag_type type);
-
-/**
- * Compares two tags, including the duration and all tag items. The
- * order of the tag items matters.
- */
-bool tag_equal(const struct tag *tag1, const struct tag *tag2);
-
-#endif
diff --git a/src/tag_ape.c b/src/tag_ape.c
deleted file mode 100644
index 0adc43092..000000000
--- a/src/tag_ape.c
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "tag_ape.h"
-#include "tag.h"
-#include "tag_table.h"
-#include "tag_handler.h"
-#include "ape.h"
-
-const struct tag_table ape_tags[] = {
- { "album artist", TAG_ALBUM_ARTIST },
- { "year", TAG_DATE },
- { NULL, TAG_NUM_OF_ITEM_TYPES }
-};
-
-static enum tag_type
-tag_ape_name_parse(const char *name)
-{
- enum tag_type type = tag_table_lookup_i(ape_tags, name);
- if (type == TAG_NUM_OF_ITEM_TYPES)
- type = tag_name_parse_i(name);
-
- return type;
-}
-
-/**
- * @return true if the item was recognized
- */
-static bool
-tag_ape_import_item(unsigned long flags,
- const char *key, const char *value, size_t value_length,
- const struct tag_handler *handler, void *handler_ctx)
-{
- /* we only care about utf-8 text tags */
- if ((flags & (0x3 << 1)) != 0)
- return false;
-
- tag_handler_invoke_pair(handler, handler_ctx, key, value);
-
- enum tag_type type = tag_ape_name_parse(key);
- if (type == TAG_NUM_OF_ITEM_TYPES)
- return false;
-
- bool recognized = false;
- const char *end = value + value_length;
- while (true) {
- /* multiple values are separated by null bytes */
- const char *n = memchr(value, 0, end - value);
- if (n != NULL) {
- if (n > value) {
- tag_handler_invoke_tag(handler, handler_ctx,
- type, value);
- recognized = true;
- }
-
- value = n + 1;
- } else {
- char *p = g_strndup(value, end - value);
- tag_handler_invoke_tag(handler, handler_ctx,
- type, p);
- g_free(p);
- recognized = true;
- break;
- }
- }
-
- return recognized;
-}
-
-struct tag_ape_ctx {
- const struct tag_handler *handler;
- void *handler_ctx;
-
- bool recognized;
-};
-
-static bool
-tag_ape_callback(unsigned long flags, const char *key,
- const char *value, size_t value_length, void *_ctx)
-{
- struct tag_ape_ctx *ctx = _ctx;
-
- ctx->recognized |= tag_ape_import_item(flags, key, value, value_length,
- ctx->handler, ctx->handler_ctx);
- return true;
-}
-
-bool
-tag_ape_scan2(const char *path_fs,
- const struct tag_handler *handler, void *handler_ctx)
-{
- struct tag_ape_ctx ctx = {
- .handler = handler,
- .handler_ctx = handler_ctx,
- .recognized = false,
- };
-
- return tag_ape_scan(path_fs, tag_ape_callback, &ctx) &&
- ctx.recognized;
-}
diff --git a/src/tag_ape.h b/src/tag_ape.h
deleted file mode 100644
index e2daf088d..000000000
--- a/src/tag_ape.h
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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_TAG_APE_H
-#define MPD_TAG_APE_H
-
-#include "tag_table.h"
-
-#include <stdbool.h>
-
-struct tag_handler;
-
-extern const struct tag_table ape_tags[];
-
-/**
- * Scan the APE tags of a file.
- *
- * @param path_fs the path of the file in filesystem encoding
- */
-bool
-tag_ape_scan2(const char *path_fs,
- const struct tag_handler *handler, void *handler_ctx);
-
-#endif
diff --git a/src/tag_file.c b/src/tag_file.c
deleted file mode 100644
index 8d8a0f5fb..000000000
--- a/src/tag_file.c
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * 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 "tag_file.h"
-#include "uri.h"
-#include "decoder_list.h"
-#include "decoder_plugin.h"
-#include "input_stream.h"
-
-#include <assert.h>
-#include <unistd.h> /* for SEEK_SET */
-
-bool
-tag_file_scan(const char *path_fs,
- const struct tag_handler *handler, void *handler_ctx)
-{
- assert(path_fs != NULL);
- assert(handler != NULL);
-
- /* check if there's a suffix and a plugin */
-
- const char *suffix = uri_get_suffix(path_fs);
- if (suffix == NULL)
- return false;
-
- const struct decoder_plugin *plugin =
- decoder_plugin_from_suffix(suffix, NULL);
- if (plugin == NULL)
- return false;
-
- struct input_stream *is = NULL;
- GMutex *mutex = NULL;
- GCond *cond = NULL;
-
- do {
- /* load file tag */
- if (decoder_plugin_scan_file(plugin, path_fs,
- handler, handler_ctx))
- break;
-
- /* fall back to stream tag */
- if (plugin->scan_stream != NULL) {
- /* open the input_stream (if not already
- open) */
- if (is == NULL) {
- mutex = g_mutex_new();
- cond = g_cond_new();
- is = input_stream_open(path_fs, mutex, cond,
- NULL);
- }
-
- /* now try the stream_tag() method */
- if (is != NULL) {
- if (decoder_plugin_scan_stream(plugin, is,
- handler,
- handler_ctx))
- break;
-
- input_stream_lock_seek(is, 0, SEEK_SET, NULL);
- }
- }
-
- plugin = decoder_plugin_from_suffix(suffix, plugin);
- } while (plugin != NULL);
-
- if (is != NULL) {
- input_stream_close(is);
- g_cond_free(cond);
- g_mutex_free(mutex);
- }
-
- return plugin != NULL;
-}
diff --git a/src/tag_file.h b/src/tag_file.h
deleted file mode 100644
index 8cf1af3cb..000000000
--- a/src/tag_file.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef MPD_TAG_FILE_H
-#define MPD_TAG_FILE_H
-
-#include "check.h"
-
-#include <stdbool.h>
-
-struct tag_handler;
-
-/**
- * Scan the tags of a song file. Invokes matching decoder plugins,
- * but does not invoke the special "APE" and "ID3" scanners.
- */
-bool
-tag_file_scan(const char *path_fs,
- const struct tag_handler *handler, void *handler_ctx);
-
-#endif
diff --git a/src/tag_handler.c b/src/tag_handler.c
deleted file mode 100644
index 316715e87..000000000
--- a/src/tag_handler.c
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "tag_handler.h"
-
-#include <glib.h>
-
-static void
-add_tag_duration(unsigned seconds, void *ctx)
-{
- struct tag *tag = ctx;
-
- tag->time = seconds;
-}
-
-static void
-add_tag_tag(enum tag_type type, const char *value, void *ctx)
-{
- struct tag *tag = ctx;
-
- tag_add_item(tag, type, value);
-}
-
-const struct tag_handler add_tag_handler = {
- .duration = add_tag_duration,
- .tag = add_tag_tag,
-};
-
-static void
-full_tag_pair(const char *name, G_GNUC_UNUSED const char *value, void *ctx)
-{
- struct tag *tag = ctx;
-
- if (g_ascii_strcasecmp(name, "cuesheet") == 0)
- tag->has_playlist = true;
-}
-
-const struct tag_handler full_tag_handler = {
- .duration = add_tag_duration,
- .tag = add_tag_tag,
- .pair = full_tag_pair,
-};
-
diff --git a/src/tag_handler.h b/src/tag_handler.h
deleted file mode 100644
index 7f15c2a48..000000000
--- a/src/tag_handler.h
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * 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_TAG_HANDLER_H
-#define MPD_TAG_HANDLER_H
-
-#include "check.h"
-#include "tag.h"
-
-#include <assert.h>
-
-/**
- * A callback table for receiving metadata of a song.
- */
-struct tag_handler {
- /**
- * Declare the duration of a song, in seconds. Do not call
- * this when the duration could not be determined, because
- * there is no magic value for "unknown duration".
- */
- void (*duration)(unsigned seconds, void *ctx);
-
- /**
- * A tag has been read.
- *
- * @param the value of the tag; the pointer will become
- * invalid after returning
- */
- void (*tag)(enum tag_type type, const char *value, void *ctx);
-
- /**
- * A name-value pair has been read. It is the codec specific
- * representation of tags.
- */
- void (*pair)(const char *key, const char *value, void *ctx);
-};
-
-static inline void
-tag_handler_invoke_duration(const struct tag_handler *handler, void *ctx,
- unsigned seconds)
-{
- assert(handler != NULL);
-
- if (handler->duration != NULL)
- handler->duration(seconds, ctx);
-}
-
-static inline void
-tag_handler_invoke_tag(const struct tag_handler *handler, void *ctx,
- enum tag_type type, const char *value)
-{
- assert(handler != NULL);
- assert((unsigned)type < TAG_NUM_OF_ITEM_TYPES);
- assert(value != NULL);
-
- if (handler->tag != NULL)
- handler->tag(type, value, ctx);
-}
-
-static inline void
-tag_handler_invoke_pair(const struct tag_handler *handler, void *ctx,
- const char *name, const char *value)
-{
- assert(handler != NULL);
- assert(name != NULL);
- assert(value != NULL);
-
- if (handler->pair != NULL)
- handler->pair(name, value, ctx);
-}
-
-/**
- * This #tag_handler implementation adds tag values to a #tag object
- * (casted from the context pointer).
- */
-extern const struct tag_handler add_tag_handler;
-
-/**
- * This #tag_handler implementation adds tag values to a #tag object
- * (casted from the context pointer), and supports the has_playlist
- * attribute.
- */
-extern const struct tag_handler full_tag_handler;
-
-#endif
diff --git a/src/tag_id3.c b/src/tag_id3.c
deleted file mode 100644
index 0971829f0..000000000
--- a/src/tag_id3.c
+++ /dev/null
@@ -1,584 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "tag_id3.h"
-#include "tag_handler.h"
-#include "tag_table.h"
-#include "tag.h"
-#include "riff.h"
-#include "aiff.h"
-#include "conf.h"
-
-#include <glib.h>
-#include <id3tag.h>
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <errno.h>
-#include <string.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "id3"
-
-# ifndef ID3_FRAME_COMPOSER
-# define ID3_FRAME_COMPOSER "TCOM"
-# endif
-# ifndef ID3_FRAME_DISC
-# define ID3_FRAME_DISC "TPOS"
-# endif
-
-#ifndef ID3_FRAME_ARTIST_SORT
-#define ID3_FRAME_ARTIST_SORT "TSOP"
-#endif
-
-#ifndef ID3_FRAME_ALBUM_ARTIST_SORT
-#define ID3_FRAME_ALBUM_ARTIST_SORT "TSO2" /* this one is unofficial, introduced by Itunes */
-#endif
-
-#ifndef ID3_FRAME_ALBUM_ARTIST
-#define ID3_FRAME_ALBUM_ARTIST "TPE2"
-#endif
-
-static inline bool
-tag_is_id3v1(struct id3_tag *tag)
-{
- return (id3_tag_options(tag, 0, 0) & ID3_TAG_OPTION_ID3V1) != 0;
-}
-
-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);
- if (field == NULL)
- return NULL;
-
- ucs4 = id3_field_getstring(field);
- if (ucs4 == NULL)
- return NULL;
-
- return id3_ucs4_utf8duplicate(ucs4);
-}
-
-/* This will try to convert a string to utf-8,
- */
-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;
-
- /* use encoding field here? */
- if (is_id3v1 &&
- (encoding = config_get_string(CONF_ID3V1_ENCODING, NULL)) != NULL) {
- isostr = id3_ucs4_latin1duplicate(ucs4);
- if (G_UNLIKELY(!isostr)) {
- return NULL;
- }
-
- utf8 = (id3_utf8_t *)
- g_convert_with_fallback((const char*)isostr, -1,
- "utf-8", encoding,
- NULL, NULL, NULL, NULL);
- if (utf8 == NULL) {
- g_debug("Unable to convert %s string to UTF-8: '%s'",
- encoding, isostr);
- g_free(isostr);
- return NULL;
- }
- g_free(isostr);
- } else {
- utf8 = id3_ucs4_utf8duplicate(ucs4);
- if (G_UNLIKELY(!utf8)) {
- return NULL;
- }
- }
-
- utf8_stripped = (id3_utf8_t *)g_strdup(g_strstrip((gchar *)utf8));
- g_free(utf8);
-
- return utf8_stripped;
-}
-
-/**
- * Import a "Text information frame" (ID3v2.4.0 section 4.2). It
- * contains 2 fields:
- *
- * - encoding
- * - string list
- */
-static void
-tag_id3_import_text_frame(struct id3_tag *tag, const struct id3_frame *frame,
- enum tag_type 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);
- if (field == NULL || field->type != ID3_FIELD_TYPE_TEXTENCODING)
- return;
-
- /* process the value(s) */
-
- field = id3_frame_field(frame, 1);
- if (field == NULL || field->type != ID3_FIELD_TYPE_STRINGLIST)
- return;
-
- /* Get the number of strings available */
- nstrings = id3_field_getnstrings(field);
- for (i = 0; i < nstrings; i++) {
- ucs4 = id3_field_getstrings(field, i);
- if (ucs4 == NULL)
- continue;
-
- if (type == TAG_GENRE)
- ucs4 = id3_genre_name(ucs4);
-
- utf8 = import_id3_string(tag_is_id3v1(tag), ucs4);
- if (utf8 == NULL)
- continue;
-
- tag_handler_invoke_tag(handler, handler_ctx,
- type, (const char *)utf8);
- g_free(utf8);
- }
-}
-
-/**
- * Import all text frames with the specified id (ID3v2.4.0 section
- * 4.2). This is a wrapper for tag_id3_import_text_frame().
- */
-static void
-tag_id3_import_text(struct id3_tag *tag, const char *id, enum tag_type type,
- const struct tag_handler *handler, void *handler_ctx)
-{
- const struct id3_frame *frame;
- for (unsigned i = 0;
- (frame = id3_tag_findframe(tag, id, i)) != NULL; ++i)
- tag_id3_import_text_frame(tag, frame, type,
- handler, handler_ctx);
-}
-
-/**
- * Import a "Comment frame" (ID3v2.4.0 section 4.10). It
- * contains 4 fields:
- *
- * - encoding
- * - language
- * - string
- * - full string (we use this one)
- */
-static void
-tag_id3_import_comment_frame(struct id3_tag *tag,
- const struct id3_frame *frame, enum tag_type type,
- 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);
- if (field == NULL)
- return;
-
- ucs4 = id3_field_getfullstring(field);
- if (ucs4 == NULL)
- return;
-
- utf8 = import_id3_string(tag_is_id3v1(tag), ucs4);
- if (utf8 == NULL)
- return;
-
- tag_handler_invoke_tag(handler, handler_ctx, type, (const char *)utf8);
- g_free(utf8);
-}
-
-/**
- * Import all comment frames (ID3v2.4.0 section 4.10). This is a
- * wrapper for tag_id3_import_comment_frame().
- */
-static void
-tag_id3_import_comment(struct id3_tag *tag, const char *id, enum tag_type type,
- const struct tag_handler *handler, void *handler_ctx)
-{
- const struct id3_frame *frame;
- for (unsigned i = 0;
- (frame = id3_tag_findframe(tag, id, i)) != NULL; ++i)
- tag_id3_import_comment_frame(tag, frame, type,
- handler, handler_ctx);
-}
-
-/**
- * Parse a TXXX name, and convert it to a tag_type enum value.
- * Returns TAG_NUM_OF_ITEM_TYPES if the TXXX name is not understood.
- */
-static enum tag_type
-tag_id3_parse_txxx_name(const char *name)
-{
- static const struct tag_table txxx_tags[] = {
- { "ALBUMARTISTSORT", TAG_ALBUM_ARTIST_SORT },
- { "MusicBrainz Artist Id", TAG_MUSICBRAINZ_ARTISTID },
- { "MusicBrainz Album Id", TAG_MUSICBRAINZ_ALBUMID },
- { "MusicBrainz Album Artist Id",
- TAG_MUSICBRAINZ_ALBUMARTISTID },
- { "MusicBrainz Track Id", TAG_MUSICBRAINZ_TRACKID },
- { NULL, TAG_NUM_OF_ITEM_TYPES }
- };
-
- return tag_table_lookup(txxx_tags, name);
-}
-
-/**
- * Import all known MusicBrainz tags from TXXX frames.
- */
-static void
-tag_id3_import_musicbrainz(struct id3_tag *id3_tag,
- const struct tag_handler *handler,
- void *handler_ctx)
-{
- for (unsigned i = 0;; ++i) {
- const struct id3_frame *frame;
- id3_utf8_t *name, *value;
- enum tag_type type;
-
- frame = id3_tag_findframe(id3_tag, "TXXX", i);
- if (frame == NULL)
- break;
-
- name = tag_id3_getstring(frame, 1);
- if (name == NULL)
- continue;
-
- value = tag_id3_getstring(frame, 2);
- if (value == NULL)
- continue;
-
- tag_handler_invoke_pair(handler, handler_ctx,
- (const char *)name,
- (const char *)value);
-
- type = tag_id3_parse_txxx_name((const char*)name);
- free(name);
-
- if (type != TAG_NUM_OF_ITEM_TYPES)
- tag_handler_invoke_tag(handler, handler_ctx,
- type, (const char*)value);
-
- free(value);
- }
-}
-
-/**
- * Imports the MusicBrainz TrackId from the UFID tag.
- */
-static void
-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);
- if (frame == NULL)
- break;
-
- field = id3_frame_field(frame, 0);
- if (field == NULL)
- continue;
-
- name = id3_field_getlatin1(field);
- if (name == NULL ||
- strcmp((const char *)name, "http://musicbrainz.org") != 0)
- continue;
-
- field = id3_frame_field(frame, 1);
- if (field == NULL)
- continue;
-
- value = id3_field_getbinarydata(field, &length);
- if (value == NULL || length == 0)
- continue;
-
- char *p = g_strndup((const char *)value, length);
- tag_handler_invoke_tag(handler, handler_ctx,
- TAG_MUSICBRAINZ_TRACKID, p);
- g_free(p);
- }
-}
-
-static void
-scan_id3_tag(struct id3_tag *tag,
- const struct tag_handler *handler, void *handler_ctx)
-{
- tag_id3_import_text(tag, ID3_FRAME_ARTIST, TAG_ARTIST,
- handler, handler_ctx);
- tag_id3_import_text(tag, ID3_FRAME_ALBUM_ARTIST,
- TAG_ALBUM_ARTIST, handler, handler_ctx);
- tag_id3_import_text(tag, ID3_FRAME_ARTIST_SORT,
- TAG_ARTIST_SORT, handler, handler_ctx);
- tag_id3_import_text(tag, ID3_FRAME_ALBUM_ARTIST_SORT,
- TAG_ALBUM_ARTIST_SORT, handler, handler_ctx);
- tag_id3_import_text(tag, ID3_FRAME_TITLE, TAG_TITLE,
- handler, handler_ctx);
- tag_id3_import_text(tag, ID3_FRAME_ALBUM, TAG_ALBUM,
- handler, handler_ctx);
- tag_id3_import_text(tag, ID3_FRAME_TRACK, TAG_TRACK,
- handler, handler_ctx);
- tag_id3_import_text(tag, ID3_FRAME_YEAR, TAG_DATE,
- handler, handler_ctx);
- tag_id3_import_text(tag, ID3_FRAME_GENRE, TAG_GENRE,
- handler, handler_ctx);
- tag_id3_import_text(tag, ID3_FRAME_COMPOSER, TAG_COMPOSER,
- handler, handler_ctx);
- tag_id3_import_text(tag, "TPE3", TAG_PERFORMER,
- handler, handler_ctx);
- tag_id3_import_text(tag, "TPE4", TAG_PERFORMER, handler, handler_ctx);
- tag_id3_import_comment(tag, ID3_FRAME_COMMENT, TAG_COMMENT,
- handler, handler_ctx);
- tag_id3_import_text(tag, ID3_FRAME_DISC, TAG_DISC,
- handler, handler_ctx);
-
- tag_id3_import_musicbrainz(tag, handler, handler_ctx);
- tag_id3_import_ufid(tag, handler, handler_ctx);
-}
-
-struct tag *tag_id3_import(struct id3_tag * tag)
-{
- struct tag *ret = tag_new();
-
- scan_id3_tag(tag, &add_tag_handler, ret);
-
- if (tag_is_empty(ret)) {
- tag_free(ret);
- ret = NULL;
- }
-
- return ret;
-}
-
-static int
-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
-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;
- 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];
- id3_byte_t *tag_buffer;
- int tag_size;
- int query_buffer_size;
- int tag_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);
- if (query_buffer_size <= 0) return NULL;
-
- /* Look for a tag header */
- tag_size = id3_tag_query(query_buffer, query_buffer_size);
- if (tag_size <= 0) return NULL;
-
- /* Found a tag. Allocate a buffer and read it in. */
- tag_buffer = g_malloc(tag_size);
- if (!tag_buffer) return NULL;
-
- tag_buffer_size = fill_buffer(tag_buffer, tag_size, stream, offset, whence);
- if (tag_buffer_size < tag_size) {
- g_free(tag_buffer);
- return NULL;
- }
-
- tag = id3_tag_parse(tag_buffer, tag_buffer_size);
-
- g_free(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);
- if (!tag) {
- return NULL;
- } else if (tag_is_id3v1(tag)) {
- /* id3v1 tags don't belong here */
- id3_tag_delete(tag);
- return NULL;
- }
-
- /* We have an id3v2 tag, so let's look for SEEK frames */
- 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));
- if (seek < 0)
- break;
-
- /* Get the tag specified by the SEEK frame */
- seektag = tag_id3_read(stream, seek, SEEK_CUR);
- if (!seektag || tag_is_id3v1(seektag))
- break;
-
- /* Replace the old tag with the new one */
- id3_tag_delete(tag);
- tag = seektag;
- }
-
- return tag;
-}
-
-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);
-
- /* Get the id3v2 tag size from the footer (located before v1tag) */
- 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);
- if (!tag)
- return v1tag;
-
- /* We have an id3v2 tag, so ditch v1tag */
- id3_tag_delete(v1tag);
-
- return tag;
-}
-
-static struct id3_tag *
-tag_id3_riff_aiff_load(FILE *file)
-{
- size_t size;
- void *buffer;
- size_t ret;
- struct id3_tag *tag;
-
- size = riff_seek_id3(file);
- if (size == 0)
- size = aiff_seek_id3(file);
- if (size == 0)
- return NULL;
-
- if (size > 4 * 1024 * 1024)
- /* too large, don't allocate so much memory */
- return NULL;
-
- buffer = g_malloc(size);
- ret = fread(buffer, size, 1, file);
- if (ret != 1) {
- g_warning("Failed to read RIFF chunk");
- g_free(buffer);
- return NULL;
- }
-
- tag = id3_tag_parse(buffer, size);
- g_free(buffer);
- return tag;
-}
-
-struct id3_tag *
-tag_id3_load(const char *path_fs, GError **error_r)
-{
- FILE *file = fopen(path_fs, "rb");
- if (file == NULL) {
- g_set_error(error_r, g_file_error_quark(), errno,
- "Failed to open file %s: %s",
- path_fs, g_strerror(errno));
- return NULL;
- }
-
- struct id3_tag *tag = tag_id3_find_from_beginning(file);
- if (tag == NULL) {
- tag = tag_id3_riff_aiff_load(file);
- if (tag == NULL)
- tag = tag_id3_find_from_end(file);
- }
-
- fclose(file);
- return tag;
-}
-
-bool
-tag_id3_scan(const char *path_fs,
- const struct tag_handler *handler, void *handler_ctx)
-{
- GError *error = NULL;
- struct id3_tag *tag = tag_id3_load(path_fs, &error);
- if (tag == NULL) {
- if (error != NULL) {
- g_warning("%s", error->message);
- g_error_free(error);
- }
-
- return false;
- }
-
- scan_id3_tag(tag, handler, handler_ctx);
- id3_tag_delete(tag);
- return true;
-}
diff --git a/src/tag_id3.h b/src/tag_id3.h
deleted file mode 100644
index 049c53ad9..000000000
--- a/src/tag_id3.h
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * 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_TAG_ID3_H
-#define MPD_TAG_ID3_H
-
-#include "check.h"
-
-#include <glib.h>
-
-#include <stdbool.h>
-
-struct tag_handler;
-struct tag;
-
-#ifdef HAVE_ID3TAG
-
-bool
-tag_id3_scan(const char *path_fs,
- const struct tag_handler *handler, void *handler_ctx);
-
-struct id3_tag;
-struct tag *tag_id3_import(struct id3_tag *);
-
-/**
- * Loads the ID3 tags from the file into a libid3tag object. The
- * return value must be freed with id3_tag_delete().
- *
- * @return NULL on error or if no ID3 tag was found in the file (no
- * GError will be set)
- */
-struct id3_tag *
-tag_id3_load(const char *path_fs, GError **error_r);
-
-#else
-
-static inline bool
-tag_id3_scan(G_GNUC_UNUSED const char *path_fs,
- G_GNUC_UNUSED const struct tag_handler *handler,
- G_GNUC_UNUSED void *handler_ctx)
-{
- return false;
-}
-
-#endif
-
-#endif
diff --git a/src/tag_internal.h b/src/tag_internal.h
deleted file mode 100644
index af05cc6b6..000000000
--- a/src/tag_internal.h
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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_TAG_INTERNAL_H
-#define MPD_TAG_INTERNAL_H
-
-#include "tag.h"
-
-#include <stdbool.h>
-
-extern bool ignore_tag_items[TAG_NUM_OF_ITEM_TYPES];
-
-#endif
diff --git a/src/tag_pool.c b/src/tag_pool.c
deleted file mode 100644
index eabf3e369..000000000
--- a/src/tag_pool.c
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "tag_pool.h"
-
-#include <assert.h>
-
-GMutex *tag_pool_lock = NULL;
-
-#define NUM_SLOTS 4096
-
-struct slot {
- struct slot *next;
- unsigned char ref;
- struct tag_item item;
-} mpd_packed;
-
-static struct slot *slots[NUM_SLOTS];
-
-static inline unsigned
-calc_hash_n(enum tag_type type, const char *p, size_t length)
-{
- unsigned hash = 5381;
-
- assert(p != NULL);
-
- while (length-- > 0)
- hash = (hash << 5) + hash + *p++;
-
- return hash ^ type;
-}
-
-static inline unsigned
-calc_hash(enum tag_type type, const char *p)
-{
- unsigned hash = 5381;
-
- assert(p != NULL);
-
- while (*p != 0)
- hash = (hash << 5) + hash + *p++;
-
- return hash ^ type;
-}
-
-static inline struct slot *
-tag_item_to_slot(struct tag_item *item)
-{
- return (struct slot*)(((char*)item) - offsetof(struct slot, item));
-}
-
-static struct slot *slot_alloc(struct slot *next,
- enum tag_type type,
- const char *value, int length)
-{
- struct slot *slot;
-
- 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;
-}
-
-void tag_pool_init(void)
-{
- g_assert(tag_pool_lock == NULL);
- tag_pool_lock = g_mutex_new();
-}
-
-void tag_pool_deinit(void)
-{
- g_assert(tag_pool_lock != NULL);
- g_mutex_free(tag_pool_lock);
- tag_pool_lock = NULL;
-}
-
-struct tag_item *
-tag_pool_get_item(enum tag_type type, const char *value, size_t length)
-{
- struct slot **slot_p, *slot;
-
- slot_p = &slots[calc_hash_n(type, value, length) % NUM_SLOTS];
- for (slot = *slot_p; slot != NULL; slot = slot->next) {
- if (slot->item.type == type &&
- length == strlen(slot->item.value) &&
- memcmp(value, slot->item.value, length) == 0 &&
- slot->ref < 0xff) {
- assert(slot->ref > 0);
- ++slot->ref;
- return &slot->item;
- }
- }
-
- slot = slot_alloc(*slot_p, type, value, length);
- *slot_p = slot;
- return &slot->item;
-}
-
-struct tag_item *tag_pool_dup_item(struct tag_item *item)
-{
- struct slot *slot = tag_item_to_slot(item);
-
- assert(slot->ref > 0);
-
- if (slot->ref < 0xff) {
- ++slot->ref;
- return item;
- } else {
- /* the reference counter overflows above 0xff;
- duplicate the item, and start with 1 */
- size_t length = strlen(item->value);
- struct slot **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_p = slot;
- return &slot->item;
- }
-}
-
-void tag_pool_put_item(struct tag_item *item)
-{
- struct slot **slot_p, *slot;
-
- slot = tag_item_to_slot(item);
- assert(slot->ref > 0);
- --slot->ref;
-
- if (slot->ref > 0)
- return;
-
- for (slot_p = &slots[calc_hash(item->type, item->value) % NUM_SLOTS];
- *slot_p != slot;
- slot_p = &(*slot_p)->next) {
- assert(*slot_p != NULL);
- }
-
- *slot_p = slot->next;
- g_free(slot);
-}
diff --git a/src/tag_pool.h b/src/tag_pool.h
deleted file mode 100644
index a96c00d85..000000000
--- a/src/tag_pool.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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_TAG_POOL_H
-#define MPD_TAG_POOL_H
-
-#include "tag.h"
-
-#include <glib.h>
-
-extern GMutex *tag_pool_lock;
-
-struct tag_item;
-
-void tag_pool_init(void);
-
-void tag_pool_deinit(void);
-
-struct tag_item *
-tag_pool_get_item(enum tag_type type, const char *value, size_t length);
-
-struct tag_item *tag_pool_dup_item(struct tag_item *item);
-
-void tag_pool_put_item(struct tag_item *item);
-
-#endif
diff --git a/src/tag_print.c b/src/tag_print.c
deleted file mode 100644
index 9a46b247a..000000000
--- a/src/tag_print.c
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "tag_print.h"
-#include "tag.h"
-#include "tag_internal.h"
-#include "client.h"
-#include "song.h"
-
-void tag_print_types(struct client *client)
-{
- int i;
-
- for (i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++) {
- if (!ignore_tag_items[i])
- client_printf(client, "tagtype: %s\n",
- tag_item_names[i]);
- }
-}
-
-void tag_print(struct client *client, const struct tag *tag)
-{
- if (tag->time >= 0)
- client_printf(client, SONG_TIME "%i\n", tag->time);
-
- for (unsigned i = 0; i < tag->num_items; i++) {
- client_printf(client, "%s: %s\n",
- tag_item_names[tag->items[i]->type],
- tag->items[i]->value);
- }
-}
diff --git a/src/tag_print.h b/src/tag_print.h
deleted file mode 100644
index b9eeeaecf..000000000
--- a/src/tag_print.h
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * 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_TAG_PRINT_H
-#define MPD_TAG_PRINT_H
-
-struct tag;
-struct client;
-
-void tag_print_types(struct client *client);
-
-void tag_print(struct client *client, const struct tag *tag);
-
-#endif
diff --git a/src/tag_rva2.c b/src/tag_rva2.c
deleted file mode 100644
index 9250311b9..000000000
--- a/src/tag_rva2.c
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "tag_rva2.h"
-#include "replay_gain_info.h"
-
-#include <stdint.h>
-#include <string.h>
-#include <glib.h>
-#include <id3tag.h>
-
-enum rva2_channel {
- CHANNEL_OTHER = 0x00,
- CHANNEL_MASTER_VOLUME = 0x01,
- CHANNEL_FRONT_RIGHT = 0x02,
- CHANNEL_FRONT_LEFT = 0x03,
- CHANNEL_BACK_RIGHT = 0x04,
- CHANNEL_BACK_LEFT = 0x05,
- CHANNEL_FRONT_CENTRE = 0x06,
- CHANNEL_BACK_CENTRE = 0x07,
- CHANNEL_SUBWOOFER = 0x08
-};
-
-struct rva2_data {
- uint8_t type;
- uint8_t volume_adjustment[2];
- uint8_t peak_bits;
-};
-
-static inline id3_length_t
-rva2_peak_bytes(const struct rva2_data *data)
-{
- return (data->peak_bits + 7) / 8;
-}
-
-static inline int
-rva2_fixed_volume_adjustment(const struct rva2_data *data)
-{
- signed int voladj_fixed;
- voladj_fixed = (data->volume_adjustment[0] << 8) |
- data->volume_adjustment[1];
- voladj_fixed |= -(voladj_fixed & 0x8000);
- return voladj_fixed;
-}
-
-static inline float
-rva2_float_volume_adjustment(const struct rva2_data *data)
-{
- /*
- * "The volume adjustment is encoded as a fixed point decibel
- * value, 16 bit signed integer representing (adjustment*512),
- * giving +/- 64 dB with a precision of 0.001953125 dB."
- */
-
- return (float)rva2_fixed_volume_adjustment(data) / (float)512;
-}
-
-static inline bool
-rva2_apply_data(struct replay_gain_info *replay_gain_info,
- const struct rva2_data *data, const id3_latin1_t *id)
-{
- if (data->type != CHANNEL_MASTER_VOLUME)
- return false;
-
- float volume_adjustment = rva2_float_volume_adjustment(data);
-
- if (strcmp((const char *)id, "album") == 0) {
- replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain = volume_adjustment;
- } else if (strcmp((const char *)id, "track") == 0) {
- replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain = volume_adjustment;
- } else {
- replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain = volume_adjustment;
- replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain = volume_adjustment;
- }
-
- return true;
-}
-
-static bool
-rva2_apply_frame(struct replay_gain_info *replay_gain_info,
- const struct id3_frame *frame)
-{
- const id3_latin1_t *id = id3_field_getlatin1(id3_frame_field(frame, 0));
- id3_length_t length;
- const id3_byte_t *data =
- id3_field_getbinarydata(id3_frame_field(frame, 1), &length);
-
- if (id == NULL || data == NULL)
- return false;
-
- /*
- * "The 'identification' string is used to identify the
- * situation and/or device where this adjustment should apply.
- * The following is then repeated for every channel
- *
- * Type of channel $xx
- * Volume adjustment $xx xx
- * Bits representing peak $xx
- * Peak volume $xx (xx ...)"
- */
-
- while (length >= 4) {
- const struct rva2_data *d = (const struct rva2_data *)data;
- unsigned int peak_bytes = rva2_peak_bytes(d);
- if (4 + peak_bytes > length)
- break;
-
- if (rva2_apply_data(replay_gain_info, d, id))
- return true;
-
- data += 4 + peak_bytes;
- length -= 4 + peak_bytes;
- }
-
- return false;
-}
-
-bool
-tag_rva2_parse(struct id3_tag *tag, struct replay_gain_info *replay_gain_info)
-{
- bool found = false;
-
- /* Loop through all RVA2 frames as some programs (e.g. mp3gain) store
- track and album gain in separate tags */
- const struct id3_frame *frame;
- for (unsigned i = 0;
- (frame = id3_tag_findframe(tag, "RVA2", i)) != NULL;
- ++i)
- if (rva2_apply_frame(replay_gain_info, frame))
- found = true;
-
- return found;
-}
diff --git a/src/tag_rva2.h b/src/tag_rva2.h
deleted file mode 100644
index 8aac2fe9f..000000000
--- a/src/tag_rva2.h
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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_TAG_RVA2_H
-#define MPD_TAG_RVA2_H
-
-#include "check.h"
-
-#include <stdbool.h>
-
-struct id3_tag;
-struct replay_gain_info;
-
-/**
- * Parse the RVA2 tag, and fill the #replay_gain_info struct. This is
- * used by decoder plugins with ID3 support.
- *
- * @return true on success
- */
-bool
-tag_rva2_parse(struct id3_tag *tag, struct replay_gain_info *replay_gain_info);
-
-#endif
diff --git a/src/tag_save.c b/src/tag_save.c
deleted file mode 100644
index 2fdaef56c..000000000
--- a/src/tag_save.c
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "tag_save.h"
-#include "tag.h"
-#include "tag_internal.h"
-#include "song.h"
-
-void tag_save(FILE *file, const struct tag *tag)
-{
- if (tag->time >= 0)
- fprintf(file, SONG_TIME "%i\n", tag->time);
-
- if (tag->has_playlist)
- fprintf(file, "Playlist: yes\n");
-
- for (unsigned i = 0; i < tag->num_items; i++)
- fprintf(file, "%s: %s\n",
- tag_item_names[tag->items[i]->type],
- tag->items[i]->value);
-}
diff --git a/src/tag_save.h b/src/tag_save.h
deleted file mode 100644
index 9f6a580c8..000000000
--- a/src/tag_save.h
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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_TAG_SAVE_H
-#define MPD_TAG_SAVE_H
-
-#include <stdio.h>
-
-struct tag;
-
-void tag_save(FILE *file, const struct tag *tag);
-
-#endif
diff --git a/src/tag_table.h b/src/tag_table.h
deleted file mode 100644
index d87d4869a..000000000
--- a/src/tag_table.h
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * 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_TAG_TABLE_H
-#define MPD_TAG_TABLE_H
-
-#include "tag.h"
-
-#include <glib.h>
-
-struct tag_table {
- const char *name;
-
- enum tag_type type;
-};
-
-/**
- * Looks up a string in a tag translation table (case sensitive).
- * Returns TAG_NUM_OF_ITEM_TYPES if the specified name was not found
- * in the table.
- */
-G_GNUC_PURE
-static inline enum tag_type
-tag_table_lookup(const struct tag_table *table, const char *name)
-{
- for (; table->name != NULL; ++table)
- if (strcmp(name, table->name) == 0)
- return table->type;
-
- return TAG_NUM_OF_ITEM_TYPES;
-}
-
-/**
- * Looks up a string in a tag translation table (case insensitive).
- * Returns TAG_NUM_OF_ITEM_TYPES if the specified name was not found
- * in the table.
- */
-G_GNUC_PURE
-static inline enum tag_type
-tag_table_lookup_i(const struct tag_table *table, const char *name)
-{
- for (; table->name != NULL; ++table)
- if (g_ascii_strcasecmp(name, table->name) == 0)
- return table->type;
-
- return TAG_NUM_OF_ITEM_TYPES;
-}
-
-#endif
diff --git a/src/text_file.c b/src/text_file.c
deleted file mode 100644
index 3674e5ce2..000000000
--- a/src/text_file.c
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "text_file.h"
-
-#include <assert.h>
-#include <string.h>
-
-char *
-read_text_line(FILE *file, GString *buffer)
-{
- enum {
- max_length = 512 * 1024,
- step = 1024,
- };
-
- gsize length = 0, i;
- char *p;
-
- assert(file != NULL);
- assert(buffer != NULL);
-
- if (buffer->allocated_len < step)
- g_string_set_size(buffer, step);
-
- while (buffer->len < max_length) {
- p = fgets(buffer->str + length,
- buffer->allocated_len - length, file);
- if (p == NULL) {
- if (length == 0 || ferror(file))
- return NULL;
- break;
- }
-
- i = strlen(buffer->str + length);
- length += i;
- if (i < step - 1 || buffer->str[length - 1] == '\n')
- break;
-
- g_string_set_size(buffer, length + step);
- }
-
- /* remove the newline characters */
- if (buffer->str[length - 1] == '\n')
- --length;
- if (buffer->str[length - 1] == '\r')
- --length;
-
- g_string_set_size(buffer, length);
- return buffer->str;
-}
diff --git a/src/text_file.h b/src/text_file.h
deleted file mode 100644
index 9dd810943..000000000
--- a/src/text_file.h
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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_TEXT_FILE_H
-#define MPD_TEXT_FILE_H
-
-#include <glib.h>
-
-#include <stdio.h>
-
-/**
- * Reads a line from the input file, and strips trailing space. There
- * is a reasonable maximum line length, only to prevent denial of
- * service.
- *
- * @param file the source file, opened in text mode
- * @param buffer an allocator for the buffer
- * @return a pointer to the line, or NULL on end-of-file or error
- */
-char *
-read_text_line(FILE *file, GString *buffer);
-
-#endif
diff --git a/src/text_input_stream.c b/src/text_input_stream.c
deleted file mode 100644
index 4a858fc85..000000000
--- a/src/text_input_stream.c
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "text_input_stream.h"
-#include "input_stream.h"
-#include "fifo_buffer.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-
-struct text_input_stream {
- struct input_stream *is;
-
- struct fifo_buffer *buffer;
-
- char *line;
-};
-
-struct text_input_stream *
-text_input_stream_new(struct input_stream *is)
-{
- struct text_input_stream *tis = g_new(struct text_input_stream, 1);
-
- tis->is = is;
- tis->buffer = fifo_buffer_new(4096);
- tis->line = NULL;
-
- return tis;
-}
-
-void
-text_input_stream_free(struct text_input_stream *tis)
-{
- fifo_buffer_free(tis->buffer);
- g_free(tis->line);
- g_free(tis);
-}
-
-const char *
-text_input_stream_read(struct text_input_stream *tis)
-{
- GError *error = NULL;
- void *dest;
- const char *src, *p;
- size_t length, nbytes;
-
- g_free(tis->line);
- tis->line = NULL;
-
- do {
- dest = fifo_buffer_write(tis->buffer, &length);
- if (dest != NULL && length >= 2) {
- /* reserve one byte for the null terminator if
- the last line is not terminated by a
- newline character */
- --length;
-
- nbytes = input_stream_lock_read(tis->is, dest, length,
- &error);
- if (nbytes > 0)
- fifo_buffer_append(tis->buffer, nbytes);
- else if (error != NULL) {
- g_warning("%s", error->message);
- g_error_free(error);
- return NULL;
- }
- } else
- nbytes = 0;
-
- src = fifo_buffer_read(tis->buffer, &length);
- if (src == NULL)
- return NULL;
-
- p = memchr(src, '\n', length);
- if (p == NULL && nbytes == 0) {
- /* end of file (or line too long): terminate
- the current line */
- dest = fifo_buffer_write(tis->buffer, &nbytes);
- assert(dest != NULL);
- *(char *)dest = '\n';
- fifo_buffer_append(tis->buffer, 1);
- }
- } while (p == NULL);
-
- length = p - src + 1;
- while (p > src && g_ascii_isspace(p[-1]))
- --p;
-
- tis->line = g_strndup(src, p - src);
- fifo_buffer_consume(tis->buffer, length);
- return tis->line;
-}
diff --git a/src/text_input_stream.h b/src/text_input_stream.h
deleted file mode 100644
index 9b3245689..000000000
--- a/src/text_input_stream.h
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * 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_TEXT_INPUT_STREAM_H
-#define MPD_TEXT_INPUT_STREAM_H
-
-struct input_stream;
-struct text_input_stream;
-
-/**
- * Wraps an existing #input_stream object into a #text_input_stream,
- * to read its contents as text lines.
- *
- * @param is an open #input_stream object
- * @return the new #text_input_stream object
- */
-struct text_input_stream *
-text_input_stream_new(struct input_stream *is);
-
-/**
- * Frees the #text_input_stream object. Does not close or free the
- * underlying #input_stream.
- */
-void
-text_input_stream_free(struct text_input_stream *tis);
-
-/**
- * Reads the next line from the stream.
- *
- * @return a line (newline character stripped), or NULL on end of file
- * or error
- */
-const char *
-text_input_stream_read(struct text_input_stream *tis);
-
-#endif
diff --git a/src/thread/Cond.hxx b/src/thread/Cond.hxx
new file mode 100644
index 000000000..bbaabddc2
--- /dev/null
+++ b/src/thread/Cond.hxx
@@ -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.
+ */
+
+#ifndef MPD_THREAD_COND_HXX
+#define MPD_THREAD_COND_HXX
+
+#ifdef WIN32
+
+/* mingw-w64 4.6.3 lacks a std::cond implementation */
+
+#include "WindowsCond.hxx"
+typedef WindowsCond Cond;
+
+#else
+
+#include "PosixCond.hxx"
+typedef PosixCond Cond;
+
+#endif
+
+#endif
diff --git a/src/thread/CriticalSection.hxx b/src/thread/CriticalSection.hxx
new file mode 100644
index 000000000..8bc05b8f5
--- /dev/null
+++ b/src/thread/CriticalSection.hxx
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2009-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 MPD_THREAD_CRITICAL_SECTION_HXX
+#define MPD_THREAD_CRITICAL_SECTION_HXX
+
+#include <windows.h>
+
+/**
+ * Wrapper for a CRITICAL_SECTION, backend for the Mutex class.
+ */
+class CriticalSection {
+ friend class WindowsCond;
+
+ CRITICAL_SECTION critical_section;
+
+public:
+ CriticalSection() {
+ ::InitializeCriticalSection(&critical_section);
+ }
+
+ ~CriticalSection() {
+ ::DeleteCriticalSection(&critical_section);
+ }
+
+ CriticalSection(const CriticalSection &other) = delete;
+ CriticalSection &operator=(const CriticalSection &other) = delete;
+
+ void lock() {
+ ::EnterCriticalSection(&critical_section);
+ };
+
+ bool try_lock() {
+ return ::TryEnterCriticalSection(&critical_section) != 0;
+ };
+
+ void unlock() {
+ ::LeaveCriticalSection(&critical_section);
+ }
+};
+
+#endif
diff --git a/src/thread/GLibCond.hxx b/src/thread/GLibCond.hxx
new file mode 100644
index 000000000..9ab08e9fd
--- /dev/null
+++ b/src/thread/GLibCond.hxx
@@ -0,0 +1,88 @@
+/*
+ * 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 MPD_THREAD_GLIB_COND_HXX
+#define MPD_THREAD_GLIB_COND_HXX
+
+#include "GLibMutex.hxx"
+
+/**
+ * A wrapper for GCond.
+ */
+class GLibCond {
+#if GLIB_CHECK_VERSION(2,32,0)
+ GCond cond;
+#else
+ GCond *cond;
+#endif
+
+public:
+ GLibCond() {
+#if GLIB_CHECK_VERSION(2,32,0)
+ g_cond_init(&cond);
+#else
+ cond = g_cond_new();
+#endif
+ }
+
+ ~GLibCond() {
+#if GLIB_CHECK_VERSION(2,32,0)
+ g_cond_clear(&cond);
+#else
+ g_cond_free(cond);
+#endif
+ }
+
+ GLibCond(const GLibCond &other) = delete;
+ GLibCond &operator=(const GLibCond &other) = delete;
+
+private:
+ GCond *GetNative() {
+#if GLIB_CHECK_VERSION(2,32,0)
+ return &cond;
+#else
+ return cond;
+#endif
+ }
+
+public:
+ void signal() {
+ g_cond_signal(GetNative());
+ }
+
+ void broadcast() {
+ g_cond_broadcast(GetNative());
+ }
+
+ void wait(GLibMutex &mutex) {
+ g_cond_wait(GetNative(), mutex.GetNative());
+ }
+};
+
+#endif
diff --git a/src/thread/GLibMutex.hxx b/src/thread/GLibMutex.hxx
new file mode 100644
index 000000000..2c666c1ea
--- /dev/null
+++ b/src/thread/GLibMutex.hxx
@@ -0,0 +1,90 @@
+/*
+ * 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 MPD_THREAD_GLIB_MUTEX_HXX
+#define MPD_THREAD_GLIB_MUTEX_HXX
+
+#include <glib.h>
+
+/**
+ * A wrapper for GMutex.
+ */
+class GLibMutex {
+ friend class GLibCond;
+
+#if GLIB_CHECK_VERSION(2,32,0)
+ GMutex mutex;
+#else
+ GMutex *mutex;
+#endif
+
+public:
+ GLibMutex() {
+#if GLIB_CHECK_VERSION(2,32,0)
+ g_mutex_init(&mutex);
+#else
+ mutex = g_mutex_new();
+#endif
+ }
+
+ ~GLibMutex() {
+#if GLIB_CHECK_VERSION(2,32,0)
+ g_mutex_clear(&mutex);
+#else
+ g_mutex_free(mutex);
+#endif
+ }
+
+ GLibMutex(const GLibMutex &other) = delete;
+ GLibMutex &operator=(const GLibMutex &other) = delete;
+
+private:
+ GMutex *GetNative() {
+#if GLIB_CHECK_VERSION(2,32,0)
+ return &mutex;
+#else
+ return mutex;
+#endif
+ }
+
+public:
+ void lock() {
+ g_mutex_lock(GetNative());
+ }
+
+ bool try_lock() {
+ return g_mutex_trylock(GetNative());
+ }
+
+ void unlock() {
+ g_mutex_lock(GetNative());
+ }
+};
+
+#endif
diff --git a/src/thread/Mutex.hxx b/src/thread/Mutex.hxx
new file mode 100644
index 000000000..675af74b1
--- /dev/null
+++ b/src/thread/Mutex.hxx
@@ -0,0 +1,54 @@
+/*
+ * 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_THREAD_MUTEX_HXX
+#define MPD_THREAD_MUTEX_HXX
+
+#ifdef WIN32
+
+/* mingw-w64 4.6.3 lacks a std::mutex implementation */
+
+#include "CriticalSection.hxx"
+typedef CriticalSection Mutex;
+
+#else
+
+#include "PosixMutex.hxx"
+
+typedef PosixMutex Mutex;
+
+#endif
+
+class ScopeLock {
+ Mutex &mutex;
+
+public:
+ ScopeLock(Mutex &_mutex):mutex(_mutex) {
+ mutex.lock();
+ };
+
+ ~ScopeLock() {
+ mutex.unlock();
+ };
+
+ ScopeLock(const ScopeLock &other) = delete;
+ ScopeLock &operator=(const ScopeLock &other) = delete;
+};
+
+#endif
diff --git a/src/thread/PosixCond.hxx b/src/thread/PosixCond.hxx
new file mode 100644
index 000000000..6f98d3ad0
--- /dev/null
+++ b/src/thread/PosixCond.hxx
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2009-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 MPD_THREAD_POSIX_COND_HXX
+#define MPD_THREAD_POSIX_COND_HXX
+
+#include "PosixMutex.hxx"
+
+#include <sys/time.h>
+
+/**
+ * Low-level wrapper for a pthread_cond_t.
+ */
+class PosixCond {
+ pthread_cond_t cond;
+
+public:
+ constexpr PosixCond():cond(PTHREAD_COND_INITIALIZER) {}
+
+ PosixCond(const PosixCond &other) = delete;
+ PosixCond &operator=(const PosixCond &other) = delete;
+
+ void signal() {
+ pthread_cond_signal(&cond);
+ }
+
+ void broadcast() {
+ pthread_cond_broadcast(&cond);
+ }
+
+ void wait(PosixMutex &mutex) {
+ pthread_cond_wait(&cond, &mutex.mutex);
+ }
+
+ bool timed_wait(PosixMutex &mutex, unsigned timeout_ms) {
+ struct timeval now;
+ gettimeofday(&now, nullptr);
+
+ struct timespec ts;
+ ts.tv_sec = now.tv_sec + timeout_ms / 1000;
+ ts.tv_nsec = (now.tv_usec + (timeout_ms % 1000) * 1000) * 1000;
+
+ return pthread_cond_timedwait(&cond, &mutex.mutex, &ts) == 0;
+ }
+};
+
+#endif
diff --git a/src/thread/PosixMutex.hxx b/src/thread/PosixMutex.hxx
new file mode 100644
index 000000000..d50764af4
--- /dev/null
+++ b/src/thread/PosixMutex.hxx
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2009-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 MPD_THREAD_POSIX_MUTEX_HXX
+#define MPD_THREAD_POSIX_MUTEX_HXX
+
+#include <pthread.h>
+
+/**
+ * Low-level wrapper for a pthread_mutex_t.
+ */
+class PosixMutex {
+ friend class PosixCond;
+
+ pthread_mutex_t mutex;
+
+public:
+ constexpr PosixMutex():mutex(PTHREAD_MUTEX_INITIALIZER) {}
+
+ PosixMutex(const PosixMutex &other) = delete;
+ PosixMutex &operator=(const PosixMutex &other) = delete;
+
+ void lock() {
+ pthread_mutex_lock(&mutex);
+ }
+
+ bool try_lock() {
+ return pthread_mutex_trylock(&mutex) == 0;
+ }
+
+ void unlock() {
+ pthread_mutex_unlock(&mutex);
+ }
+};
+
+#endif
diff --git a/src/thread/WindowsCond.hxx b/src/thread/WindowsCond.hxx
new file mode 100644
index 000000000..c05bc05b2
--- /dev/null
+++ b/src/thread/WindowsCond.hxx
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2009-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 MPD_THREAD_WINDOWS_COND_HXX
+#define MPD_THREAD_WINDOWS_COND_HXX
+
+#include "CriticalSection.hxx"
+
+/**
+ * Wrapper for a CONDITION_VARIABLE, backend for the Cond class.
+ */
+class WindowsCond {
+ CONDITION_VARIABLE cond;
+
+public:
+ WindowsCond() {
+ InitializeConditionVariable(&cond);
+ }
+
+ WindowsCond(const WindowsCond &other) = delete;
+ WindowsCond &operator=(const WindowsCond &other) = delete;
+
+ void signal() {
+ WakeConditionVariable(&cond);
+ }
+
+ void broadcast() {
+ WakeAllConditionVariable(&cond);
+ }
+
+ bool timed_wait(CriticalSection &mutex, DWORD timeout_ms) {
+ return SleepConditionVariableCS(&cond, &mutex.critical_section,
+ timeout_ms);
+ }
+
+ void wait(CriticalSection &mutex) {
+ timed_wait(mutex, INFINITE);
+ }
+};
+
+#endif
diff --git a/src/timer.c b/src/timer.c
deleted file mode 100644
index 9a3228465..000000000
--- a/src/timer.c
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "timer.h"
-#include "audio_format.h"
-#include "clock.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <limits.h>
-#include <stddef.h>
-
-struct timer *timer_new(const struct audio_format *af)
-{
- struct timer *timer = g_new(struct timer, 1);
- timer->time = 0; // us
- timer->started = 0; // false
- timer->rate = af->sample_rate * audio_format_frame_size(af); // samples per second
-
- return timer;
-}
-
-void timer_free(struct timer *timer)
-{
- g_free(timer);
-}
-
-void timer_start(struct timer *timer)
-{
- timer->time = monotonic_clock_us();
- timer->started = 1;
-}
-
-void timer_reset(struct timer *timer)
-{
- timer->time = 0;
- timer->started = 0;
-}
-
-void timer_add(struct timer *timer, int size)
-{
- assert(timer->started);
-
- // (size samples) / (rate samples per second) = duration seconds
- // duration seconds * 1000000 = duration us
- timer->time += ((uint64_t)size * 1000000) / timer->rate;
-}
-
-unsigned
-timer_delay(const struct timer *timer)
-{
- int64_t delay = (int64_t)(timer->time - monotonic_clock_us()) / 1000;
- if (delay < 0)
- return 0;
-
- if (delay > G_MAXINT)
- delay = G_MAXINT;
-
- return delay;
-}
-
-void timer_sync(struct timer *timer)
-{
- int64_t sleep_duration;
-
- assert(timer->started);
-
- sleep_duration = timer->time - monotonic_clock_us();
- if (sleep_duration > 0)
- g_usleep(sleep_duration);
-}
diff --git a/src/timer.h b/src/timer.h
deleted file mode 100644
index 184881249..000000000
--- a/src/timer.h
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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_TIMER_H
-#define MPD_TIMER_H
-
-#include <stdint.h>
-
-struct audio_format;
-
-struct timer {
- uint64_t time;
- int started;
- int rate;
-};
-
-struct timer *timer_new(const struct audio_format *af);
-
-void timer_free(struct timer *timer);
-
-void timer_start(struct timer *timer);
-
-void timer_reset(struct timer *timer);
-
-void timer_add(struct timer *timer, int size);
-
-/**
- * Returns the number of milliseconds to sleep to get back to sync.
- */
-unsigned
-timer_delay(const struct timer *timer);
-
-void timer_sync(struct timer *timer);
-
-#endif
diff --git a/src/tokenizer.c b/src/tokenizer.c
deleted file mode 100644
index bbb34e100..000000000
--- a/src/tokenizer.c
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "tokenizer.h"
-#include "string_util.h"
-
-#include <stdbool.h>
-#include <assert.h>
-#include <string.h>
-
-G_GNUC_CONST
-static GQuark
-tokenizer_quark(void)
-{
- return g_quark_from_static_string("tokenizer");
-}
-
-static inline bool
-valid_word_first_char(char ch)
-{
- return g_ascii_isalpha(ch);
-}
-
-static inline bool
-valid_word_char(char ch)
-{
- return g_ascii_isalnum(ch) || ch == '_';
-}
-
-char *
-tokenizer_next_word(char **input_p, GError **error_r)
-{
- char *word, *input;
-
- assert(input_p != NULL);
- assert(*input_p != NULL);
-
- word = input = *input_p;
-
- if (*input == 0)
- return NULL;
-
- /* check the first character */
-
- if (!valid_word_first_char(*input)) {
- g_set_error(error_r, tokenizer_quark(), 0,
- "Letter expected");
- return NULL;
- }
-
- /* now iterate over the other characters until we find a
- whitespace or end-of-string */
-
- while (*++input != 0) {
- if (g_ascii_isspace(*input)) {
- /* a whitespace: the word ends here */
- *input = 0;
- /* skip all following spaces, too */
- input = strchug_fast(input + 1);
- break;
- }
-
- if (!valid_word_char(*input)) {
- *input_p = input;
- g_set_error(error_r, tokenizer_quark(), 0,
- "Invalid word character");
- return NULL;
- }
- }
-
- /* end of string: the string is already null-terminated
- here */
-
- *input_p = input;
- return word;
-}
-
-static inline bool
-valid_unquoted_char(char ch)
-{
- return (unsigned char)ch > 0x20 && ch != '"' && ch != '\'';
-}
-
-char *
-tokenizer_next_unquoted(char **input_p, GError **error_r)
-{
- char *word, *input;
-
- assert(input_p != NULL);
- assert(*input_p != NULL);
-
- word = input = *input_p;
-
- if (*input == 0)
- return NULL;
-
- /* check the first character */
-
- if (!valid_unquoted_char(*input)) {
- g_set_error(error_r, tokenizer_quark(), 0,
- "Invalid unquoted character");
- return NULL;
- }
-
- /* now iterate over the other characters until we find a
- whitespace or end-of-string */
-
- while (*++input != 0) {
- if (g_ascii_isspace(*input)) {
- /* a whitespace: the word ends here */
- *input = 0;
- /* skip all following spaces, too */
- input = strchug_fast(input + 1);
- break;
- }
-
- if (!valid_unquoted_char(*input)) {
- *input_p = input;
- g_set_error(error_r, tokenizer_quark(), 0,
- "Invalid unquoted character");
- return NULL;
- }
- }
-
- /* end of string: the string is already null-terminated
- here */
-
- *input_p = input;
- return word;
-}
-
-char *
-tokenizer_next_string(char **input_p, GError **error_r)
-{
- char *word, *dest, *input;
-
- assert(input_p != NULL);
- assert(*input_p != NULL);
-
- word = dest = input = *input_p;
-
- if (*input == 0)
- /* end of line */
- return NULL;
-
- /* check for the opening " */
-
- if (*input != '"') {
- g_set_error(error_r, tokenizer_quark(), 0,
- "'\"' expected");
- return NULL;
- }
-
- ++input;
-
- /* copy all characters */
-
- while (*input != '"') {
- if (*input == '\\')
- /* the backslash escapes the following
- character */
- ++input;
-
- if (*input == 0) {
- /* return input-1 so the caller can see the
- difference between "end of line" and
- "error" */
- *input_p = input - 1;
- g_set_error(error_r, tokenizer_quark(), 0,
- "Missing closing '\"'");
- return NULL;
- }
-
- /* copy one character */
- *dest++ = *input++;
- }
-
- /* the following character must be a whitespace (or end of
- line) */
-
- ++input;
- if (*input != 0 && !g_ascii_isspace(*input)) {
- *input_p = input;
- g_set_error(error_r, tokenizer_quark(), 0,
- "Space expected after closing '\"'");
- return NULL;
- }
-
- /* finish the string and return it */
-
- *dest = 0;
- *input_p = strchug_fast(input);
- return word;
-}
-
-char *
-tokenizer_next_param(char **input_p, GError **error_r)
-{
- assert(input_p != NULL);
- assert(*input_p != NULL);
-
- if (**input_p == '"')
- return tokenizer_next_string(input_p, error_r);
- else
- return tokenizer_next_unquoted(input_p, error_r);
-}
diff --git a/src/tokenizer.h b/src/tokenizer.h
deleted file mode 100644
index d55eb3ca6..000000000
--- a/src/tokenizer.h
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * 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_TOKENIZER_H
-#define MPD_TOKENIZER_H
-
-#include <glib.h>
-
-/**
- * Reads the next word from the input string. This function modifies
- * the input string.
- *
- * @param input_p the input string; this function returns a pointer to
- * the first non-whitespace character of the following token
- * @param error_r if this function returns NULL and **input_p!=0, it
- * optionally provides a GError object in this argument
- * @return a pointer to the null-terminated word, or NULL on error or
- * end of line
- */
-char *
-tokenizer_next_word(char **input_p, GError **error_r);
-
-/**
- * Reads the next unquoted word from the input string. This function
- * modifies the input string.
- *
- * @param input_p the input string; this function returns a pointer to
- * the first non-whitespace character of the following token
- * @param error_r if this function returns NULL and **input_p!=0, it
- * optionally provides a GError object in this argument
- * @return a pointer to the null-terminated word, or NULL on error or
- * end of line
- */
-char *
-tokenizer_next_unquoted(char **input_p, GError **error_r);
-
-/**
- * Reads the next quoted string from the input string. A backslash
- * escapes the following character. This function modifies the input
- * string.
- *
- * @param input_p the input string; this function returns a pointer to
- * the first non-whitespace character of the following token
- * @param error_r if this function returns NULL and **input_p!=0, it
- * optionally provides a GError object in this argument
- * @return a pointer to the null-terminated string, or NULL on error
- * or end of line
- */
-char *
-tokenizer_next_string(char **input_p, GError **error_r);
-
-/**
- * Reads the next unquoted word or quoted string from the input. This
- * is a wrapper for tokenizer_next_unquoted() and
- * tokenizer_next_string().
- *
- * @param input_p the input string; this function returns a pointer to
- * the first non-whitespace character of the following token
- * @param error_r if this function returns NULL and **input_p!=0, it
- * optionally provides a GError object in this argument
- * @return a pointer to the null-terminated string, or NULL on error
- * or end of line
- */
-char *
-tokenizer_next_param(char **input_p, GError **error_r);
-
-#endif
diff --git a/src/update.c b/src/update.c
deleted file mode 100644
index 12eec40c4..000000000
--- a/src/update.c
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "update.h"
-#include "update_queue.h"
-#include "update_walk.h"
-#include "update_remove.h"
-#include "update.h"
-#include "database.h"
-#include "mapper.h"
-#include "playlist.h"
-#include "event_pipe.h"
-#include "update.h"
-#include "idle.h"
-#include "stats.h"
-#include "main.h"
-#include "mpd_error.h"
-
-#include <glib.h>
-
-#include <assert.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "update"
-
-static enum update_progress {
- UPDATE_PROGRESS_IDLE = 0,
- UPDATE_PROGRESS_RUNNING = 1,
- UPDATE_PROGRESS_DONE = 2
-} progress;
-
-static bool modified;
-
-static GThread *update_thr;
-
-static const unsigned update_task_id_max = 1 << 15;
-
-static unsigned update_task_id;
-
-/* XXX this flag is passed to update_task() */
-static bool discard;
-
-unsigned
-isUpdatingDB(void)
-{
- return (progress != UPDATE_PROGRESS_IDLE) ? update_task_id : 0;
-}
-
-static void * update_task(void *_path)
-{
- const char *path = _path;
-
- if (path != NULL && *path != 0)
- g_debug("starting: %s", path);
- else
- g_debug("starting");
-
- modified = update_walk(path, discard);
-
- if (modified || !db_exists()) {
- GError *error = NULL;
- if (!db_save(&error)) {
- g_warning("Failed to save database: %s",
- error->message);
- g_error_free(error);
- }
- }
-
- if (path != NULL && *path != 0)
- g_debug("finished: %s", path);
- else
- g_debug("finished");
- g_free(_path);
-
- progress = UPDATE_PROGRESS_DONE;
- event_pipe_emit(PIPE_EVENT_UPDATE);
- return NULL;
-}
-
-static void
-spawn_update_task(const char *path)
-{
- GError *e = NULL;
-
- assert(g_thread_self() == main_task);
-
- progress = UPDATE_PROGRESS_RUNNING;
- modified = false;
-
- update_thr = g_thread_create(update_task, g_strdup(path), TRUE, &e);
- if (update_thr == NULL)
- MPD_ERROR("Failed to spawn update task: %s", e->message);
-
- if (++update_task_id > update_task_id_max)
- update_task_id = 1;
- g_debug("spawned thread for update job id %i", update_task_id);
-}
-
-unsigned
-update_enqueue(const char *path, bool _discard)
-{
- assert(g_thread_self() == main_task);
-
- if (!mapper_has_music_directory())
- return 0;
-
- if (progress != UPDATE_PROGRESS_IDLE) {
- unsigned next_task_id =
- update_queue_push(path, discard, update_task_id);
- if (next_task_id == 0)
- return 0;
-
- return next_task_id > update_task_id_max ? 1 : next_task_id;
- }
-
- discard = _discard;
- spawn_update_task(path);
-
- idle_add(IDLE_UPDATE);
-
- return update_task_id;
-}
-
-/**
- * Called in the main thread after the database update is finished.
- */
-static void update_finished_event(void)
-{
- char *path;
-
- assert(progress == UPDATE_PROGRESS_DONE);
-
- g_thread_join(update_thr);
-
- idle_add(IDLE_UPDATE);
-
- if (modified) {
- /* send "idle" events */
- playlist_increment_version_all(&g_playlist);
- idle_add(IDLE_DATABASE);
- }
-
- path = update_queue_shift(&discard);
- if (path != NULL) {
- /* schedule the next path */
- spawn_update_task(path);
- g_free(path);
- } else {
- progress = UPDATE_PROGRESS_IDLE;
-
- stats_update();
- }
-}
-
-void update_global_init(void)
-{
- event_pipe_register(PIPE_EVENT_UPDATE, update_finished_event);
-
- update_remove_global_init();
- update_walk_global_init();
-}
-
-void update_global_finish(void)
-{
- update_walk_global_finish();
- update_remove_global_finish();
-}
diff --git a/src/update.h b/src/update.h
deleted file mode 100644
index 3d586b694..000000000
--- a/src/update.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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_UPDATE_H
-#define MPD_UPDATE_H
-
-#include <stdbool.h>
-
-void update_global_init(void);
-
-void update_global_finish(void);
-
-unsigned
-isUpdatingDB(void);
-
-/**
- * Add this path to the database update queue.
- *
- * @param path a path to update; if NULL or an empty string,
- * the whole music directory is updated
- * @return the job id, or 0 on error
- */
-unsigned
-update_enqueue(const char *path, bool discard);
-
-#endif
diff --git a/src/update_archive.c b/src/update_archive.c
deleted file mode 100644
index 3fb2bc18c..000000000
--- a/src/update_archive.c
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * 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" /* must be first for large file support */
-#include "update_archive.h"
-#include "update_internal.h"
-#include "db_lock.h"
-#include "directory.h"
-#include "song.h"
-#include "mapper.h"
-#include "archive_list.h"
-#include "archive_plugin.h"
-
-#include <glib.h>
-
-#include <string.h>
-
-static void
-update_archive_tree(struct directory *directory, char *name)
-{
- char *tmp = strchr(name, '/');
- if (tmp) {
- *tmp = 0;
- //add dir is not there already
- db_lock();
- struct directory *subdir =
- directory_make_child(directory, name);
- subdir->device = DEVICE_INARCHIVE;
- db_unlock();
- //create directories first
- update_archive_tree(subdir, tmp+1);
- } else {
- if (strlen(name) == 0) {
- g_warning("archive returned directory only");
- return;
- }
-
- //add file
- db_lock();
- struct song *song = directory_get_song(directory, name);
- db_unlock();
- if (song == NULL) {
- song = song_file_load(name, directory);
- if (song != NULL) {
- db_lock();
- directory_add_song(directory, song);
- db_unlock();
-
- modified = true;
- g_message("added %s/%s",
- directory_get_path(directory), name);
- }
- }
- }
-}
-
-/**
- * Updates the file listing from an archive file.
- *
- * @param parent the parent directory the archive file resides in
- * @param name the UTF-8 encoded base name of the archive file
- * @param st stat() information on the archive file
- * @param plugin the archive plugin which fits this archive type
- */
-static void
-update_archive_file2(struct directory *parent, const char *name,
- const struct stat *st,
- const struct archive_plugin *plugin)
-{
- db_lock();
- struct directory *directory = directory_get_child(parent, name);
- db_unlock();
-
- if (directory != NULL && directory->mtime == st->st_mtime &&
- !walk_discard)
- /* MPD has already scanned the archive, and it hasn't
- changed since - don't consider updating it */
- return;
-
- char *path_fs = map_directory_child_fs(parent, name);
-
- /* open archive */
- GError *error = NULL;
- struct archive_file *file = archive_file_open(plugin, path_fs, &error);
- if (file == NULL) {
- g_free(path_fs);
- g_warning("%s", error->message);
- g_error_free(error);
- return;
- }
-
- g_debug("archive %s opened", path_fs);
- g_free(path_fs);
-
- if (directory == NULL) {
- g_debug("creating archive directory: %s", name);
- db_lock();
- directory = directory_new_child(parent, name);
- /* mark this directory as archive (we use device for
- this) */
- directory->device = DEVICE_INARCHIVE;
- db_unlock();
- }
-
- directory->mtime = st->st_mtime;
-
- archive_file_scan_reset(file);
-
- char *filepath;
- while ((filepath = archive_file_scan_next(file)) != NULL) {
- /* split name into directory and file */
- g_debug("adding archive file: %s", filepath);
- update_archive_tree(directory, filepath);
- }
-
- archive_file_close(file);
-}
-
-bool
-update_archive_file(struct directory *directory,
- const char *name, const char *suffix,
- const struct stat *st)
-{
-#ifdef ENABLE_ARCHIVE
- const struct archive_plugin *plugin =
- archive_plugin_from_suffix(suffix);
- if (plugin == NULL)
- return false;
-
- update_archive_file2(directory, name, st, plugin);
- return true;
-#else
- (void)directory;
- (void)name;
- (void)suffix;
- (void)st;
-
- return false;
-#endif
-}
diff --git a/src/update_archive.h b/src/update_archive.h
deleted file mode 100644
index 838697dd9..000000000
--- a/src/update_archive.h
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef MPD_UPDATE_ARCHIVE_H
-#define MPD_UPDATE_ARCHIVE_H
-
-#include "check.h"
-
-#include <stdbool.h>
-#include <sys/stat.h>
-
-struct directory;
-struct archive_plugin;
-
-#ifdef ENABLE_ARCHIVE
-
-bool
-update_archive_file(struct directory *directory,
- const char *name, const char *suffix,
- const struct stat *st);
-
-#else
-
-#include <glib.h>
-
-static inline bool
-update_archive_file(G_GNUC_UNUSED struct directory *directory,
- G_GNUC_UNUSED const char *name,
- G_GNUC_UNUSED const char *suffix,
- G_GNUC_UNUSED const struct stat *st)
-{
- return false;
-}
-
-#endif
-
-#endif
diff --git a/src/update_container.c b/src/update_container.c
deleted file mode 100644
index bda95dabe..000000000
--- a/src/update_container.c
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * 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" /* must be first for large file support */
-#include "update_container.h"
-#include "update_internal.h"
-#include "update_db.h"
-#include "db_lock.h"
-#include "directory.h"
-#include "song.h"
-#include "mapper.h"
-#include "decoder_plugin.h"
-#include "tag.h"
-#include "tag_handler.h"
-
-#include <glib.h>
-
-/**
- * Create the specified directory object if it does not exist already
- * or if the #stat object indicates that it has been modified since
- * the last update. Returns NULL when it exists already and is
- * unmodified.
- *
- * The caller must lock the database.
- */
-static struct directory *
-make_directory_if_modified(struct directory *parent, const char *name,
- const struct stat *st)
-{
- struct directory *directory = directory_get_child(parent, name);
-
- // directory exists already
- if (directory != NULL) {
- if (directory->mtime == st->st_mtime && !walk_discard) {
- /* not modified */
- db_unlock();
- return NULL;
- }
-
- delete_directory(directory);
- modified = true;
- }
-
- directory = directory_make_child(parent, name);
- directory->mtime = st->st_mtime;
- return directory;
-}
-
-bool
-update_container_file(struct directory *directory,
- const char *name,
- const struct stat *st,
- const struct decoder_plugin *plugin)
-{
- if (plugin->container_scan == NULL)
- return false;
-
- db_lock();
- struct directory *contdir =
- make_directory_if_modified(directory, name, st);
- if (contdir == NULL) {
- /* not modified */
- db_unlock();
- return true;
- }
-
- contdir->device = DEVICE_CONTAINER;
- db_unlock();
-
- char *const pathname = map_directory_child_fs(directory, name);
-
- char *vtrack;
- unsigned int tnum = 0;
- while ((vtrack = plugin->container_scan(pathname, ++tnum)) != NULL) {
- struct song *song = song_file_new(vtrack, contdir);
-
- // shouldn't be necessary but it's there..
- song->mtime = st->st_mtime;
-
- char *child_path_fs = map_directory_child_fs(contdir, vtrack);
-
- song->tag = tag_new();
- decoder_plugin_scan_file(plugin, child_path_fs,
- &add_tag_handler, song->tag);
- g_free(child_path_fs);
-
- db_lock();
- directory_add_song(contdir, song);
- db_unlock();
-
- modified = true;
-
- g_message("added %s/%s",
- directory_get_path(directory), vtrack);
- g_free(vtrack);
- }
-
- g_free(pathname);
-
- if (tnum == 1) {
- db_lock();
- delete_directory(contdir);
- db_unlock();
- return false;
- } else
- return true;
-}
diff --git a/src/update_container.h b/src/update_container.h
deleted file mode 100644
index 7f42c80ca..000000000
--- a/src/update_container.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef MPD_UPDATE_CONTAINER_H
-#define MPD_UPDATE_CONTAINER_H
-
-#include "check.h"
-
-#include <stdbool.h>
-#include <sys/stat.h>
-
-struct directory;
-struct decoder_plugin;
-
-bool
-update_container_file(struct directory *directory,
- const char *name,
- const struct stat *st,
- const struct decoder_plugin *plugin);
-
-#endif
diff --git a/src/update_db.c b/src/update_db.c
deleted file mode 100644
index 8982a53e2..000000000
--- a/src/update_db.c
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * 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" /* must be first for large file support */
-#include "update_db.h"
-#include "update_remove.h"
-#include "directory.h"
-#include "song.h"
-#include "playlist_vector.h"
-#include "db_lock.h"
-
-#include <glib.h>
-#include <assert.h>
-
-void
-delete_song(struct directory *dir, struct song *del)
-{
- assert(del->parent == dir);
-
- /* first, prevent traversers in main task from getting this */
- directory_remove_song(dir, del);
-
- db_unlock(); /* temporary unlock, because update_remove_song() blocks */
-
- /* now take it out of the playlist (in the main_task) */
- update_remove_song(del);
-
- /* finally, all possible references gone, free it */
- song_free(del);
-
- db_lock();
-}
-
-/**
- * Recursively remove all sub directories and songs from a directory,
- * leaving an empty directory.
- *
- * Caller must lock the #db_mutex.
- */
-static void
-clear_directory(struct directory *directory)
-{
- struct directory *child, *n;
- directory_for_each_child_safe(child, n, directory)
- delete_directory(child);
-
- struct song *song, *ns;
- directory_for_each_song_safe(song, ns, directory) {
- assert(song->parent == directory);
- delete_song(directory, song);
- }
-}
-
-void
-delete_directory(struct directory *directory)
-{
- assert(directory->parent != NULL);
-
- clear_directory(directory);
-
- directory_delete(directory);
-}
-
-bool
-delete_name_in(struct directory *parent, const char *name)
-{
- bool modified = false;
-
- db_lock();
- struct directory *directory = directory_get_child(parent, name);
-
- if (directory != NULL) {
- delete_directory(directory);
- modified = true;
- }
-
- struct song *song = directory_get_song(parent, name);
- if (song != NULL) {
- delete_song(parent, song);
- modified = true;
- }
-
- playlist_vector_remove(&parent->playlists, name);
-
- db_unlock();
-
- return modified;
-}
diff --git a/src/update_db.h b/src/update_db.h
deleted file mode 100644
index 0a9e46b05..000000000
--- a/src/update_db.h
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef MPD_UPDATE_DB_H
-#define MPD_UPDATE_DB_H
-
-#include "check.h"
-
-#include <stdbool.h>
-
-struct directory;
-struct song;
-
-/**
- * Caller must lock the #db_mutex.
- */
-void
-delete_song(struct directory *parent, struct song *song);
-
-/**
- * Recursively free a directory and all its contents.
- *
- * Caller must lock the #db_mutex.
- */
-void
-delete_directory(struct directory *directory);
-
-/**
- * Caller must NOT lock the #db_mutex.
- *
- * @return true if the database was modified
- */
-bool
-delete_name_in(struct directory *parent, const char *name);
-
-#endif
diff --git a/src/update_internal.h b/src/update_internal.h
deleted file mode 100644
index 76ab7bed3..000000000
--- a/src/update_internal.h
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * 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_UPDATE_INTERNAL_H
-#define MPD_UPDATE_INTERNAL_H
-
-#include "check.h"
-
-#include <stdbool.h>
-
-extern bool walk_discard;
-extern bool modified;
-
-#endif
diff --git a/src/update_io.c b/src/update_io.c
deleted file mode 100644
index c6a540a0f..000000000
--- a/src/update_io.c
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * 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" /* must be first for large file support */
-#include "update_io.h"
-#include "mapper.h"
-#include "directory.h"
-#include "glib_compat.h"
-
-#include <glib.h>
-
-#include <errno.h>
-#include <unistd.h>
-
-int
-stat_directory(const struct directory *directory, struct stat *st)
-{
- char *path_fs = map_directory_fs(directory);
- if (path_fs == NULL)
- return -1;
-
- int ret = stat(path_fs, st);
- if (ret < 0)
- g_warning("Failed to stat %s: %s", path_fs, g_strerror(errno));
-
- g_free(path_fs);
- return ret;
-}
-
-int
-stat_directory_child(const struct directory *parent, const char *name,
- struct stat *st)
-{
- char *path_fs = map_directory_child_fs(parent, name);
- if (path_fs == NULL)
- return -1;
-
- int ret = stat(path_fs, st);
- if (ret < 0)
- g_warning("Failed to stat %s: %s", path_fs, g_strerror(errno));
-
- g_free(path_fs);
- return ret;
-}
-
-bool
-directory_exists(const struct directory *directory)
-{
- char *path_fs = map_directory_fs(directory);
- if (path_fs == NULL)
- /* invalid path: cannot exist */
- return false;
-
- GFileTest test = directory->device == DEVICE_INARCHIVE ||
- directory->device == DEVICE_CONTAINER
- ? G_FILE_TEST_IS_REGULAR
- : G_FILE_TEST_IS_DIR;
-
- bool exists = g_file_test(path_fs, test);
- g_free(path_fs);
-
- return exists;
-}
-
-bool
-directory_child_is_regular(const struct directory *directory,
- const char *name_utf8)
-{
- char *path_fs = map_directory_child_fs(directory, name_utf8);
- if (path_fs == NULL)
- return false;
-
- struct stat st;
- bool is_regular = stat(path_fs, &st) == 0 && S_ISREG(st.st_mode);
- g_free(path_fs);
-
- return is_regular;
-}
-
-bool
-directory_child_access(const struct directory *directory,
- const char *name, int mode)
-{
-#ifdef WIN32
- /* access() is useless on WIN32 */
- (void)directory;
- (void)name;
- (void)mode;
- return true;
-#else
- char *path = map_directory_child_fs(directory, name);
- if (path == NULL)
- /* something went wrong, but that isn't a permission
- problem */
- return true;
-
- bool success = access(path, mode) == 0 || errno != EACCES;
- g_free(path);
- return success;
-#endif
-}
diff --git a/src/update_io.h b/src/update_io.h
deleted file mode 100644
index 6ff1ccebd..000000000
--- a/src/update_io.h
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef MPD_UPDATE_IO_H
-#define MPD_UPDATE_IO_H
-
-#include "check.h"
-
-#include <stdbool.h>
-#include <sys/stat.h>
-
-struct directory;
-
-int
-stat_directory(const struct directory *directory, struct stat *st);
-
-int
-stat_directory_child(const struct directory *parent, const char *name,
- struct stat *st);
-
-bool
-directory_exists(const struct directory *directory);
-
-bool
-directory_child_is_regular(const struct directory *directory,
- const char *name_utf8);
-
-/**
- * Checks if the given permissions on the mapped file are given.
- */
-bool
-directory_child_access(const struct directory *directory,
- const char *name, int mode);
-
-#endif
diff --git a/src/update_queue.c b/src/update_queue.c
deleted file mode 100644
index 2150fa4e4..000000000
--- a/src/update_queue.c
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "update_queue.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-
-/* make this dynamic?, or maybe this is big enough... */
-static struct {
- char *path;
- bool discard;
-} update_queue[32];
-
-static size_t update_queue_length;
-
-unsigned
-update_queue_push(const char *path, bool discard, unsigned base)
-{
- assert(update_queue_length <= G_N_ELEMENTS(update_queue));
-
- if (update_queue_length == G_N_ELEMENTS(update_queue))
- return 0;
-
- update_queue[update_queue_length].path = g_strdup(path);
- update_queue[update_queue_length].discard = discard;
-
- ++update_queue_length;
-
- return base + update_queue_length;
-}
-
-char *
-update_queue_shift(bool *discard_r)
-{
- char *path;
-
- if (update_queue_length == 0)
- return NULL;
-
- path = update_queue[0].path;
- *discard_r = update_queue[0].discard;
-
- memmove(&update_queue[0], &update_queue[1],
- --update_queue_length * sizeof(update_queue[0]));
- return path;
-}
diff --git a/src/update_queue.h b/src/update_queue.h
deleted file mode 100644
index 84ba474b0..000000000
--- a/src/update_queue.h
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef MPD_UPDATE_QUEUE_H
-#define MPD_UPDATE_QUEUE_H
-
-#include "check.h"
-
-#include <stdbool.h>
-
-unsigned
-update_queue_push(const char *path, bool discard, unsigned base);
-
-char *
-update_queue_shift(bool *discard_r);
-
-#endif
diff --git a/src/update_remove.c b/src/update_remove.c
deleted file mode 100644
index f443f5eb2..000000000
--- a/src/update_remove.c
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h" /* must be first for large file support */
-#include "update_remove.h"
-#include "event_pipe.h"
-#include "song.h"
-#include "playlist.h"
-#include "main.h"
-
-#ifdef ENABLE_SQLITE
-#include "sticker.h"
-#include "song_sticker.h"
-#endif
-
-#include <glib.h>
-
-#include <assert.h>
-
-static const struct song *removed_song;
-
-static GMutex *remove_mutex;
-static GCond *remove_cond;
-
-/**
- * Safely remove a song from the database. This must be done in the
- * main task, to be sure that there is no pointer left to it.
- */
-static void
-song_remove_event(void)
-{
- char *uri;
-
- assert(removed_song != NULL);
-
- uri = song_get_uri(removed_song);
- g_message("removing %s", uri);
- g_free(uri);
-
-#ifdef ENABLE_SQLITE
- /* if the song has a sticker, remove it */
- if (sticker_enabled())
- sticker_song_delete(removed_song);
-#endif
-
- playlist_delete_song(&g_playlist, global_player_control, removed_song);
-
- /* clear "removed_song" and send signal to update thread */
- g_mutex_lock(remove_mutex);
- removed_song = NULL;
- g_cond_signal(remove_cond);
- g_mutex_unlock(remove_mutex);
-}
-
-void
-update_remove_global_init(void)
-{
- remove_mutex = g_mutex_new();
- remove_cond = g_cond_new();
-
- event_pipe_register(PIPE_EVENT_DELETE, song_remove_event);
-}
-
-void
-update_remove_global_finish(void)
-{
- g_mutex_free(remove_mutex);
- g_cond_free(remove_cond);
-}
-
-void
-update_remove_song(const struct song *song)
-{
- assert(removed_song == NULL);
-
- removed_song = song;
-
- event_pipe_emit(PIPE_EVENT_DELETE);
-
- g_mutex_lock(remove_mutex);
-
- while (removed_song != NULL)
- g_cond_wait(remove_cond, remove_mutex);
-
- g_mutex_unlock(remove_mutex);
-}
diff --git a/src/update_remove.h b/src/update_remove.h
deleted file mode 100644
index 479ef83ff..000000000
--- a/src/update_remove.h
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef MPD_UPDATE_REMOVE_H
-#define MPD_UPDATE_REMOVE_H
-
-#include "check.h"
-
-struct song;
-
-void
-update_remove_global_init(void);
-
-void
-update_remove_global_finish(void);
-
-/**
- * Sends a signal to the main thread which will in turn remove the
- * song: from the sticker database and from the playlist. This
- * serialized access is implemented to avoid excessive locking.
- */
-void
-update_remove_song(const struct song *song);
-
-#endif
diff --git a/src/update_song.c b/src/update_song.c
deleted file mode 100644
index f5930d688..000000000
--- a/src/update_song.c
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * 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" /* must be first for large file support */
-#include "update_song.h"
-#include "update_internal.h"
-#include "update_io.h"
-#include "update_db.h"
-#include "update_container.h"
-#include "db_lock.h"
-#include "directory.h"
-#include "song.h"
-#include "decoder_list.h"
-#include "decoder_plugin.h"
-
-#include <glib.h>
-
-#include <unistd.h>
-
-static void
-update_song_file2(struct directory *directory,
- const char *name, const struct stat *st,
- const struct decoder_plugin *plugin)
-{
- db_lock();
- struct song *song = directory_get_song(directory, name);
- db_unlock();
-
- if (!directory_child_access(directory, name, R_OK)) {
- g_warning("no read permissions on %s/%s",
- directory_get_path(directory), name);
- if (song != NULL) {
- db_lock();
- delete_song(directory, song);
- db_unlock();
- }
-
- return;
- }
-
- if (!(song != NULL && st->st_mtime == song->mtime &&
- !walk_discard) &&
- update_container_file(directory, name, st, plugin)) {
- if (song != NULL) {
- db_lock();
- delete_song(directory, song);
- db_unlock();
- }
-
- return;
- }
-
- if (song == NULL) {
- g_debug("reading %s/%s",
- directory_get_path(directory), name);
- song = song_file_load(name, directory);
- if (song == NULL) {
- g_debug("ignoring unrecognized file %s/%s",
- directory_get_path(directory), name);
- return;
- }
-
- db_lock();
- directory_add_song(directory, song);
- db_unlock();
-
- modified = true;
- g_message("added %s/%s",
- directory_get_path(directory), name);
- } else if (st->st_mtime != song->mtime || walk_discard) {
- g_message("updating %s/%s",
- directory_get_path(directory), name);
- if (!song_file_update(song)) {
- g_debug("deleting unrecognized file %s/%s",
- directory_get_path(directory), name);
- db_lock();
- delete_song(directory, song);
- db_unlock();
- }
-
- modified = true;
- }
-}
-
-bool
-update_song_file(struct directory *directory,
- const char *name, const char *suffix,
- const struct stat *st)
-{
- const struct decoder_plugin *plugin =
- decoder_plugin_from_suffix(suffix, NULL);
- if (plugin == NULL)
- return false;
-
- update_song_file2(directory, name, st, plugin);
- return true;
-}
diff --git a/src/update_song.h b/src/update_song.h
deleted file mode 100644
index cff63f576..000000000
--- a/src/update_song.h
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef MPD_UPDATE_SONG_H
-#define MPD_UPDATE_SONG_H
-
-#include "check.h"
-
-#include <stdbool.h>
-#include <sys/stat.h>
-
-struct directory;
-
-bool
-update_song_file(struct directory *directory,
- const char *name, const char *suffix,
- const struct stat *st);
-
-#endif
diff --git a/src/update_walk.c b/src/update_walk.c
deleted file mode 100644
index 8554e8f3c..000000000
--- a/src/update_walk.c
+++ /dev/null
@@ -1,508 +0,0 @@
-/*
- * 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" /* must be first for large file support */
-#include "update_walk.h"
-#include "update_io.h"
-#include "update_db.h"
-#include "update_song.h"
-#include "update_archive.h"
-#include "database.h"
-#include "db_lock.h"
-#include "exclude.h"
-#include "directory.h"
-#include "song.h"
-#include "playlist_vector.h"
-#include "uri.h"
-#include "mapper.h"
-#include "path.h"
-#include "playlist_list.h"
-#include "conf.h"
-
-#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>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "update"
-
-bool walk_discard;
-bool modified;
-
-#ifndef WIN32
-
-enum {
- DEFAULT_FOLLOW_INSIDE_SYMLINKS = true,
- DEFAULT_FOLLOW_OUTSIDE_SYMLINKS = true,
-};
-
-static bool follow_inside_symlinks;
-static bool follow_outside_symlinks;
-
-#endif
-
-void
-update_walk_global_init(void)
-{
-#ifndef WIN32
- follow_inside_symlinks =
- config_get_bool(CONF_FOLLOW_INSIDE_SYMLINKS,
- DEFAULT_FOLLOW_INSIDE_SYMLINKS);
-
- follow_outside_symlinks =
- config_get_bool(CONF_FOLLOW_OUTSIDE_SYMLINKS,
- DEFAULT_FOLLOW_OUTSIDE_SYMLINKS);
-#endif
-}
-
-void
-update_walk_global_finish(void)
-{
-}
-
-static void
-directory_set_stat(struct directory *dir, const struct stat *st)
-{
- dir->inode = st->st_ino;
- dir->device = st->st_dev;
- dir->have_stat = true;
-}
-
-static void
-remove_excluded_from_directory(struct directory *directory,
- GSList *exclude_list)
-{
- db_lock();
-
- struct directory *child, *n;
- directory_for_each_child_safe(child, n, directory) {
- char *name_fs = utf8_to_fs_charset(directory_get_name(child));
-
- if (exclude_list_check(exclude_list, name_fs)) {
- delete_directory(child);
- modified = true;
- }
-
- g_free(name_fs);
- }
-
- struct song *song, *ns;
- directory_for_each_song_safe(song, ns, directory) {
- assert(song->parent == directory);
-
- char *name_fs = utf8_to_fs_charset(song->uri);
- if (exclude_list_check(exclude_list, name_fs)) {
- delete_song(directory, song);
- modified = true;
- }
-
- g_free(name_fs);
- }
-
- db_unlock();
-}
-
-static void
-purge_deleted_from_directory(struct directory *directory)
-{
- struct directory *child, *n;
- directory_for_each_child_safe(child, n, directory) {
- if (directory_exists(child))
- continue;
-
- db_lock();
- delete_directory(child);
- db_unlock();
-
- modified = true;
- }
-
- struct song *song, *ns;
- directory_for_each_song_safe(song, ns, directory) {
- char *path;
- struct stat st;
- if ((path = map_song_fs(song)) == NULL ||
- stat(path, &st) < 0 || !S_ISREG(st.st_mode)) {
- db_lock();
- delete_song(directory, song);
- db_unlock();
-
- modified = true;
- }
-
- g_free(path);
- }
-
- struct playlist_metadata *pm, *np;
- directory_for_each_playlist_safe(pm, np, directory) {
- if (!directory_child_is_regular(directory, pm->name)) {
- db_lock();
- playlist_vector_remove(&directory->playlists, pm->name);
- db_unlock();
- }
- }
-}
-
-#ifndef G_OS_WIN32
-static int
-update_directory_stat(struct directory *directory)
-{
- struct stat st;
- if (stat_directory(directory, &st) < 0)
- return -1;
-
- directory_set_stat(directory, &st);
- return 0;
-}
-#endif
-
-static int
-find_inode_ancestor(struct directory *parent, ino_t inode, dev_t device)
-{
-#ifndef G_OS_WIN32
- while (parent) {
- if (!parent->have_stat && update_directory_stat(parent) < 0)
- return -1;
-
- if (parent->inode == inode && parent->device == device) {
- g_debug("recursive directory found");
- return 1;
- }
-
- parent = parent->parent;
- }
-#else
- (void)parent;
- (void)inode;
- (void)device;
-#endif
-
- return 0;
-}
-
-static bool
-update_playlist_file2(struct directory *directory,
- const char *name, const char *suffix,
- const struct stat *st)
-{
- if (!playlist_suffix_supported(suffix))
- return false;
-
- db_lock();
- if (playlist_vector_update_or_add(&directory->playlists, name,
- st->st_mtime))
- modified = true;
- db_unlock();
- return true;
-}
-
-static bool
-update_regular_file(struct directory *directory,
- const char *name, const struct stat *st)
-{
- const char *suffix = uri_get_suffix(name);
- if (suffix == NULL)
- return false;
-
- return update_song_file(directory, name, suffix, st) ||
- update_archive_file(directory, name, suffix, st) ||
- update_playlist_file2(directory, name, suffix, st);
-}
-
-static bool
-update_directory(struct directory *directory, const struct stat *st);
-
-static void
-update_directory_child(struct directory *directory,
- const char *name, const struct stat *st)
-{
- assert(strchr(name, '/') == NULL);
-
- if (S_ISREG(st->st_mode)) {
- update_regular_file(directory, name, st);
- } else if (S_ISDIR(st->st_mode)) {
- if (find_inode_ancestor(directory, st->st_ino, st->st_dev))
- return;
-
- db_lock();
- struct directory *subdir =
- directory_make_child(directory, name);
- db_unlock();
-
- assert(directory == subdir->parent);
-
- if (!update_directory(subdir, st)) {
- db_lock();
- delete_directory(subdir);
- db_unlock();
- }
- } else {
- g_debug("update: %s is not a directory, archive or music", name);
- }
-}
-
-/* we don't look at "." / ".." nor files with newlines in their name */
-G_GNUC_PURE
-static bool skip_path(const char *path)
-{
- return (path[0] == '.' && path[1] == 0) ||
- (path[0] == '.' && path[1] == '.' && path[2] == 0) ||
- strchr(path, '\n') != NULL;
-}
-
-G_GNUC_PURE
-static bool
-skip_symlink(const struct directory *directory, const char *utf8_name)
-{
-#ifndef WIN32
- char *path_fs = map_directory_child_fs(directory, utf8_name);
- if (path_fs == NULL)
- return true;
-
- char buffer[MPD_PATH_MAX];
- ssize_t length = readlink(path_fs, buffer, sizeof(buffer));
- g_free(path_fs);
- if (length < 0)
- /* don't skip if this is not a symlink */
- return errno != EINVAL;
-
- if ((size_t)length >= sizeof(buffer))
- /* skip symlinks when the buffer is too small for the
- link target */
- return true;
-
- /* null-terminate the buffer, because readlink() will not */
- buffer[length] = 0;
-
- if (!follow_inside_symlinks && !follow_outside_symlinks) {
- /* ignore all symlinks */
- return true;
- } else if (follow_inside_symlinks && follow_outside_symlinks) {
- /* consider all symlinks */
- return false;
- }
-
- if (g_path_is_absolute(buffer)) {
- /* if the symlink points to an absolute path, see if
- that path is inside the music directory */
- const char *relative = map_to_relative_path(buffer);
- return relative > buffer
- ? !follow_inside_symlinks
- : !follow_outside_symlinks;
- }
-
- const char *p = buffer;
- while (*p == '.') {
- if (p[1] == '.' && G_IS_DIR_SEPARATOR(p[2])) {
- /* "../" moves to parent directory */
- directory = directory->parent;
- if (directory == NULL) {
- /* we have moved outside the music
- directory - skip this symlink
- if such symlinks are not allowed */
- return !follow_outside_symlinks;
- }
- p += 3;
- } else if (G_IS_DIR_SEPARATOR(p[1]))
- /* eliminate "./" */
- p += 2;
- else
- break;
- }
-
- /* we are still in the music directory, so this symlink points
- to a song which is already in the database - skip according
- to the follow_inside_symlinks param*/
- return !follow_inside_symlinks;
-#else
- /* no symlink checking on WIN32 */
-
- (void)directory;
- (void)utf8_name;
-
- return false;
-#endif
-}
-
-static bool
-update_directory(struct directory *directory, const struct stat *st)
-{
- assert(S_ISDIR(st->st_mode));
-
- directory_set_stat(directory, st);
-
- char *path_fs = map_directory_fs(directory);
- if (path_fs == NULL)
- return false;
-
- DIR *dir = opendir(path_fs);
- if (!dir) {
- g_warning("Failed to open directory %s: %s",
- path_fs, g_strerror(errno));
- g_free(path_fs);
- return false;
- }
-
- char *exclude_path_fs = g_build_filename(path_fs, ".mpdignore", NULL);
- GSList *exclude_list = exclude_list_load(exclude_path_fs);
- g_free(exclude_path_fs);
-
- g_free(path_fs);
-
- if (exclude_list != NULL)
- remove_excluded_from_directory(directory, exclude_list);
-
- purge_deleted_from_directory(directory);
-
- struct dirent *ent;
- while ((ent = readdir(dir))) {
- char *utf8;
- struct stat st2;
-
- if (skip_path(ent->d_name) ||
- exclude_list_check(exclude_list, ent->d_name))
- continue;
-
- utf8 = fs_charset_to_utf8(ent->d_name);
- if (utf8 == NULL)
- continue;
-
- if (skip_symlink(directory, utf8)) {
- modified |= delete_name_in(directory, utf8);
- g_free(utf8);
- continue;
- }
-
- if (stat_directory_child(directory, utf8, &st2) == 0)
- update_directory_child(directory, utf8, &st2);
- else
- modified |= delete_name_in(directory, utf8);
-
- g_free(utf8);
- }
-
- exclude_list_free(exclude_list);
-
- closedir(dir);
-
- directory->mtime = st->st_mtime;
-
- return true;
-}
-
-static struct directory *
-directory_make_child_checked(struct directory *parent, const char *name_utf8)
-{
- db_lock();
- struct directory *directory = directory_get_child(parent, name_utf8);
- db_unlock();
-
- if (directory != NULL)
- return directory;
-
- struct stat st;
- if (stat_directory_child(parent, name_utf8, &st) < 0 ||
- find_inode_ancestor(parent, st.st_ino, st.st_dev))
- return NULL;
-
- if (skip_symlink(parent, name_utf8))
- return NULL;
-
- /* if we're adding directory paths, make sure to delete filenames
- with potentially the same name */
- db_lock();
- struct song *conflicting = directory_get_song(parent, name_utf8);
- if (conflicting)
- delete_song(parent, conflicting);
-
- directory = directory_new_child(parent, name_utf8);
- db_unlock();
-
- directory_set_stat(directory, &st);
- return directory;
-}
-
-static struct directory *
-directory_make_uri_parent_checked(const char *uri)
-{
- struct directory *directory = db_get_root();
- char *duplicated = g_strdup(uri);
- char *name_utf8 = duplicated, *slash;
-
- while ((slash = strchr(name_utf8, '/')) != NULL) {
- *slash = 0;
-
- if (*name_utf8 == 0)
- continue;
-
- directory = directory_make_child_checked(directory, name_utf8);
- if (directory == NULL)
- break;
-
- name_utf8 = slash + 1;
- }
-
- g_free(duplicated);
- return directory;
-}
-
-static void
-update_uri(const char *uri)
-{
- struct directory *parent = directory_make_uri_parent_checked(uri);
- if (parent == NULL)
- return;
-
- char *name = g_path_get_basename(uri);
-
- struct stat st;
- if (!skip_symlink(parent, name) &&
- stat_directory_child(parent, name, &st) == 0)
- update_directory_child(parent, name, &st);
- else
- modified |= delete_name_in(parent, name);
-
- g_free(name);
-}
-
-bool
-update_walk(const char *path, bool discard)
-{
- walk_discard = discard;
- modified = false;
-
- if (path != NULL && !isRootDirectory(path)) {
- update_uri(path);
- } else {
- struct directory *directory = db_get_root();
- struct stat st;
-
- if (stat_directory(directory, &st) == 0)
- update_directory(directory, &st);
- }
-
- return modified;
-}
diff --git a/src/update_walk.h b/src/update_walk.h
deleted file mode 100644
index ab1e41fb9..000000000
--- a/src/update_walk.h
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef MPD_UPDATE_WALK_H
-#define MPD_UPDATE_WALK_H
-
-#include "check.h"
-
-#include <stdbool.h>
-
-void
-update_walk_global_init(void);
-
-void
-update_walk_global_finish(void);
-
-/**
- * Returns true if the database was modified.
- */
-bool
-update_walk(const char *path, bool discard);
-
-#endif
diff --git a/src/uri.c b/src/uri.c
deleted file mode 100644
index 2a0ca6ca6..000000000
--- a/src/uri.c
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "uri.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-
-bool uri_has_scheme(const char *uri)
-{
- return strstr(uri, "://") != NULL;
-}
-
-/* suffixes should be ascii only characters */
-const char *
-uri_get_suffix(const char *uri)
-{
- const char *suffix = strrchr(uri, '.');
- if (suffix == NULL)
- return NULL;
-
- ++suffix;
-
- if (strpbrk(suffix, "/\\") != NULL)
- return NULL;
-
- return suffix;
-}
-
-static const char *
-verify_uri_segment(const char *p)
-{
- const char *q;
-
- unsigned dots = 0;
- while (*p == '.') {
- ++p;
- ++dots;
- }
-
- if (dots <= 2 && (*p == 0 || *p == '/'))
- return NULL;
-
- q = strchr(p + 1, '/');
- return q != NULL ? q : "";
-}
-
-bool
-uri_safe_local(const char *uri)
-{
- while (true) {
- uri = verify_uri_segment(uri);
- if (uri == NULL)
- return false;
-
- if (*uri == 0)
- return true;
-
- assert(*uri == '/');
-
- ++uri;
- }
-}
-
-char *
-uri_remove_auth(const char *uri)
-{
- const char *auth, *slash, *at;
- char *p;
-
- if (strncmp(uri, "http://", 7) == 0)
- auth = uri + 7;
- else if (strncmp(uri, "https://", 8) == 0)
- auth = uri + 8;
- else
- /* unrecognized URI */
- return NULL;
-
- slash = strchr(auth, '/');
- if (slash == NULL)
- slash = auth + strlen(auth);
-
- at = memchr(auth, '@', slash - auth);
- if (at == NULL)
- /* no auth info present, do nothing */
- return NULL;
-
- /* duplicate the full URI and then delete the auth
- information */
- p = g_strdup(uri);
- memmove(p + (auth - uri), p + (at + 1 - uri),
- strlen(at));
-
- return p;
-}
diff --git a/src/uri.h b/src/uri.h
deleted file mode 100644
index 5a9b472f5..000000000
--- a/src/uri.h
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * 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_URI_H
-#define MPD_URI_H
-
-#include <glib.h>
-
-#include <stdbool.h>
-
-/**
- * Checks whether the specified URI has a scheme in the form
- * "scheme://".
- */
-G_GNUC_PURE
-bool uri_has_scheme(const char *uri);
-
-G_GNUC_PURE
-const char *
-uri_get_suffix(const char *uri);
-
-/**
- * Returns true if this is a safe "local" URI:
- *
- * - non-empty
- * - does not begin or end with a slash
- * - no double slashes
- * - no path component begins with a dot
- */
-G_GNUC_PURE
-bool
-uri_safe_local(const char *uri);
-
-/**
- * Removes HTTP username and password from the URI. This may be
- * useful for displaying an URI without disclosing secrets. Returns
- * NULL if nothing needs to be removed, or if the URI is not
- * recognized.
- */
-G_GNUC_MALLOC
-char *
-uri_remove_auth(const char *uri);
-
-#endif
diff --git a/src/util/HugeAllocator.cxx b/src/util/HugeAllocator.cxx
new file mode 100644
index 000000000..d1c55c965
--- /dev/null
+++ b/src/util/HugeAllocator.cxx
@@ -0,0 +1,87 @@
+/*
+ * 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 "HugeAllocator.hxx"
+
+#ifdef __linux__
+#include <sys/mman.h>
+#include <unistd.h>
+#else
+#include <stdlib.h>
+#endif
+
+#ifdef __linux__
+
+/**
+ * Round up the parameter, make it page-aligned.
+ */
+gcc_const
+static size_t
+AlignToPageSize(size_t size)
+{
+ static const long page_size = sysconf(_SC_PAGESIZE);
+ if (page_size > 0)
+ return size;
+
+ size_t ps(page_size);
+ return (size + ps - 1) / ps * ps;
+}
+
+void *
+HugeAllocate(size_t size)
+{
+ size = AlignToPageSize(size);
+
+ constexpr int flags = MAP_ANONYMOUS|MAP_PRIVATE|MAP_NORESERVE;
+ void *p = mmap(nullptr, size,
+ PROT_READ|PROT_WRITE, flags,
+ -1, 0);
+ if (p == (void *)-1)
+ return nullptr;
+
+#ifdef MADV_HUGEPAGE
+ /* allow the Linux kernel to use "Huge Pages", which reduces page
+ table overhead for this big chunk of data */
+ madvise(p, size, MADV_HUGEPAGE);
+#endif
+
+#ifdef MADV_DONTFORK
+ /* just in case MPD needs to fork, don't copy this allocation
+ to the child process, to reduce overhead */
+ madvise(p, size, MADV_DONTFORK);
+#endif
+
+ return p;
+}
+
+void
+HugeFree(void *p, size_t size)
+{
+ munmap(p, AlignToPageSize(size));
+}
+
+void
+HugeDiscard(void *p, size_t size)
+{
+#ifdef MADV_DONTNEED
+ madvise(p, AlignToPageSize(size), MADV_DONTNEED);
+#endif
+}
+
+#endif
diff --git a/src/util/HugeAllocator.hxx b/src/util/HugeAllocator.hxx
new file mode 100644
index 000000000..01c92cd43
--- /dev/null
+++ b/src/util/HugeAllocator.hxx
@@ -0,0 +1,82 @@
+/*
+ * 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_HUGE_ALLOCATOR_HXX
+#define MPD_HUGE_ALLOCATOR_HXX
+
+#include "gcc.h"
+
+#include <stddef.h>
+
+#ifdef __linux__
+
+/**
+ * Allocate a huge amount of memory. This will be done in a way that
+ * allows giving the memory back to the kernel as soon as we don't
+ * need it anymore. On the downside, this call is expensive.
+ */
+gcc_malloc
+void *
+HugeAllocate(size_t size);
+
+/**
+ * @param p an allocation returned by HugeAllocate()
+ * @param size the allocation's size as passed to HugeAllocate()
+ */
+void
+HugeFree(void *p, size_t size);
+
+/**
+ * Discard any data stored in the allocation and give the memory back
+ * to the kernel. After returning, the allocation still exists and
+ * can be reused at any time, but its contents are undefined.
+ *
+ * @param p an allocation returned by HugeAllocate()
+ * @param size the allocation's size as passed to HugeAllocate()
+ */
+void
+HugeDiscard(void *p, size_t size);
+
+#else
+
+/* not Linux: fall back to standard C calls */
+
+#include <stdlib.h>
+
+gcc_malloc
+static inline void *
+HugeAllocate(size_t size)
+{
+ return malloc(size);
+}
+
+static inline void
+HugeFree(void *p, size_t)
+{
+ free(p);
+}
+
+static inline void
+HugeDiscard(void *, size_t)
+{
+}
+
+#endif
+
+#endif
diff --git a/src/util/LazyRandomEngine.cxx b/src/util/LazyRandomEngine.cxx
new file mode 100644
index 000000000..0f90ebb2e
--- /dev/null
+++ b/src/util/LazyRandomEngine.cxx
@@ -0,0 +1,31 @@
+/*
+ * 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 "LazyRandomEngine.hxx"
+
+void
+LazyRandomEngine::AutoCreate()
+{
+ if (engine != nullptr)
+ return;
+
+ std::random_device rd;
+ engine = new std::mt19937(rd());
+}
diff --git a/src/util/LazyRandomEngine.hxx b/src/util/LazyRandomEngine.hxx
new file mode 100644
index 000000000..8afe1d1c0
--- /dev/null
+++ b/src/util/LazyRandomEngine.hxx
@@ -0,0 +1,67 @@
+/*
+ * 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_RANDOM_ENGINE_HXX
+#define MPD_LAZY_RANDOM_ENGINE_HXX
+
+#include "check.h"
+
+#include <random>
+
+#include <assert.h>
+
+/**
+ * A random engine that will be created and seeded on demand.
+ */
+class LazyRandomEngine {
+ std::mt19937 *engine;
+
+public:
+ typedef std::mt19937::result_type result_type;
+
+ LazyRandomEngine():engine(nullptr) {}
+ ~LazyRandomEngine() {
+ delete engine;
+ }
+
+ LazyRandomEngine(const LazyRandomEngine &other) = delete;
+ LazyRandomEngine &operator=(const LazyRandomEngine &other) = delete;
+
+ /**
+ * Create and seed the real engine. Call this before any
+ * other method.
+ */
+ void AutoCreate();
+
+ result_type min() const {
+ return engine->min();
+ }
+
+ result_type max() const {
+ return engine->max();
+ }
+
+ result_type operator()() {
+ assert(engine != nullptr);
+
+ return engine->operator()();
+ }
+};
+
+#endif
diff --git a/src/util/Manual.hxx b/src/util/Manual.hxx
new file mode 100644
index 000000000..798bc3e24
--- /dev/null
+++ b/src/util/Manual.hxx
@@ -0,0 +1,120 @@
+/*
+ * 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 MPD_MANUAL_HXX
+#define MPD_MANUAL_HXX
+
+#include "gcc.h"
+
+#include <new>
+
+#if !defined(__clang__) && __GNUC__ && !GCC_CHECK_VERSION(4,8)
+#include <type_traits>
+#endif
+
+#include <assert.h>
+
+#if defined(__clang__) || GCC_CHECK_VERSION(4,7)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wstrict-aliasing"
+#endif
+
+/**
+ * Container for an object that gets constructed and destructed
+ * manually. The object is constructed in-place, and therefore
+ * without allocation overhead. It can be constructed and destructed
+ * repeatedly.
+ */
+template<class T>
+class Manual {
+#if !defined(__clang__) && __GNUC__ && !GCC_CHECK_VERSION(4,8)
+ /* no alignas() on gcc < 4.8: apply worst-case fallback */
+ __attribute__((aligned(8)))
+#else
+ alignas(T)
+#endif
+ char data[sizeof(T)];
+
+#ifndef NDEBUG
+ bool initialized;
+#endif
+
+public:
+#ifndef NDEBUG
+ Manual():initialized(false) {}
+ ~Manual() {
+ assert(!initialized);
+ }
+#endif
+
+ template<typename... Args>
+ void Construct(Args&&... args) {
+ assert(!initialized);
+
+ void *p = data;
+ new(p) T(std::forward<Args>(args)...);
+
+#ifndef NDEBUG
+ initialized = true;
+#endif
+ }
+
+ void Destruct() {
+ assert(initialized);
+
+ T *t = (T *)data;
+ t->T::~T();
+
+#ifndef NDEBUG
+ initialized = false;
+#endif
+ }
+
+ operator T &() {
+ return *(T *)data;
+ }
+
+ operator const T &() const {
+ return *(const T *)data;
+ }
+
+ T *operator->() {
+ return (T *)data;
+ }
+
+ const T *operator->() const {
+ return (T *)data;
+ }
+};
+
+#if defined(__clang__) || GCC_VERSION >= 40700
+#pragma GCC diagnostic pop
+#endif
+
+#endif
diff --git a/src/util/PeakBuffer.cxx b/src/util/PeakBuffer.cxx
new file mode 100644
index 000000000..a3659b8f4
--- /dev/null
+++ b/src/util/PeakBuffer.cxx
@@ -0,0 +1,143 @@
+/*
+ * 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 "PeakBuffer.hxx"
+#include "HugeAllocator.hxx"
+#include "fifo_buffer.h"
+
+#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);
+}
+
+bool
+PeakBuffer::IsEmpty() const
+{
+ return (normal_buffer == nullptr ||
+ fifo_buffer_is_empty(normal_buffer)) &&
+ (peak_buffer == nullptr ||
+ fifo_buffer_is_empty(peak_buffer));
+}
+
+const void *
+PeakBuffer::Read(size_t *length_r) const
+{
+ if (normal_buffer != nullptr) {
+ const void *p = fifo_buffer_read(normal_buffer, length_r);
+ if (p != nullptr)
+ return p;
+ }
+
+ if (peak_buffer != nullptr) {
+ const void *p = fifo_buffer_read(peak_buffer, length_r);
+ if (p != nullptr)
+ return p;
+ }
+
+ return nullptr;
+}
+
+void
+PeakBuffer::Consume(size_t length)
+{
+ if (normal_buffer != nullptr && !fifo_buffer_is_empty(normal_buffer)) {
+ fifo_buffer_consume(normal_buffer, 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);
+ peak_buffer = nullptr;
+ }
+
+ return;
+ }
+}
+
+static size_t
+AppendTo(fifo_buffer *buffer, const void *data, size_t length)
+{
+ assert(data != nullptr);
+ assert(length > 0);
+
+ size_t total = 0;
+
+ do {
+ size_t max_length;
+ void *p = fifo_buffer_write(buffer, &max_length);
+ if (p == nullptr)
+ break;
+
+ const size_t nbytes = std::min(length, max_length);
+ memcpy(p, data, nbytes);
+ fifo_buffer_append(buffer, nbytes);
+
+ data = (const uint8_t *)data + nbytes;
+ length -= nbytes;
+ total += nbytes;
+ } while (length > 0);
+
+ return total;
+}
+
+bool
+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);
+ return nbytes == length;
+ }
+
+ if (normal_buffer == nullptr)
+ normal_buffer = fifo_buffer_new(normal_size);
+
+ size_t nbytes = AppendTo(normal_buffer, data, length);
+ if (nbytes > 0) {
+ data = (const uint8_t *)data + nbytes;
+ length -= nbytes;
+ if (length == 0)
+ return true;
+ }
+
+ if (peak_buffer == nullptr && peak_size > 0) {
+ peak_buffer = (fifo_buffer *)HugeAllocate(peak_size);
+ if (peak_buffer == nullptr)
+ return false;
+
+ fifo_buffer_init(peak_buffer, peak_size);
+ }
+
+ nbytes = AppendTo(peak_buffer, data, length);
+ return nbytes == length;
+}
diff --git a/src/util/PeakBuffer.hxx b/src/util/PeakBuffer.hxx
new file mode 100644
index 000000000..0fbba8d77
--- /dev/null
+++ b/src/util/PeakBuffer.hxx
@@ -0,0 +1,66 @@
+/*
+ * 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_PEAK_BUFFER_HXX
+#define MPD_PEAK_BUFFER_HXX
+
+#include "gcc.h"
+
+#include <stddef.h>
+
+struct fifo_buffer;
+
+/**
+ * A FIFO-like buffer that will allocate more memory on demand to
+ * allow large peaks. This second buffer will be given back to the
+ * kernel when it has been consumed.
+ */
+class PeakBuffer {
+ size_t normal_size, peak_size;
+
+ fifo_buffer *normal_buffer, *peak_buffer;
+
+public:
+ PeakBuffer(size_t _normal_size, size_t _peak_size)
+ :normal_size(_normal_size), peak_size(_peak_size),
+ normal_buffer(nullptr), peak_buffer(nullptr) {}
+
+ PeakBuffer(PeakBuffer &&other)
+ :normal_size(other.normal_size), peak_size(other.peak_size),
+ normal_buffer(other.normal_buffer),
+ peak_buffer(other.peak_buffer) {
+ other.normal_buffer = nullptr;
+ other.peak_buffer = nullptr;
+ }
+
+ ~PeakBuffer();
+
+ PeakBuffer(const PeakBuffer &) = delete;
+ PeakBuffer &operator=(const PeakBuffer &) = delete;
+
+ gcc_pure
+ bool IsEmpty() const;
+
+ const void *Read(size_t *length_r) const;
+ void Consume(size_t length);
+
+ bool Append(const void *data, size_t length);
+};
+
+#endif
diff --git a/src/util/RefCount.hxx b/src/util/RefCount.hxx
new file mode 100644
index 000000000..9a45a585b
--- /dev/null
+++ b/src/util/RefCount.hxx
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2003-2013 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
+ *
+ * A very simple reference counting library.
+ */
+
+#ifndef MPD_REFCOUNT_HXX
+#define MPD_REFCOUNT_HXX
+
+#include <atomic>
+
+class RefCount {
+ std::atomic_uint n;
+
+public:
+ constexpr RefCount():n(1) {}
+
+ void Increment() {
+ ++n;
+ }
+
+ /**
+ * @return true if the number of references has been dropped to 0
+ */
+ bool Decrement() {
+ return --n == 0;
+ }
+};
+
+#endif
diff --git a/src/util/SliceBuffer.hxx b/src/util/SliceBuffer.hxx
new file mode 100644
index 000000000..c61f164f4
--- /dev/null
+++ b/src/util/SliceBuffer.hxx
@@ -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.
+ */
+
+#ifndef MPD_SLICE_BUFFER_HXX
+#define MPD_SLICE_BUFFER_HXX
+
+#include "HugeAllocator.hxx"
+#include "gcc.h"
+
+#include <utility>
+#include <new>
+
+#include <assert.h>
+#include <stddef.h>
+
+/**
+ * This class pre-allocates a certain number of objects, and allows
+ * callers to allocate and free these objects ("slices").
+ */
+template<typename T>
+class SliceBuffer {
+ union Slice {
+ Slice *next;
+
+ T value;
+ };
+
+ /**
+ * The maximum number of slices in this container.
+ */
+ const unsigned n_max;
+
+ /**
+ * The number of slices that are initialized. This is used to
+ * avoid page faulting on the new allocation, so the kernel
+ * does not need to reserve physical memory pages.
+ */
+ unsigned n_initialized;
+
+ /**
+ * The number of slices currently allocated.
+ */
+ unsigned n_allocated;
+
+ Slice *const data;
+
+ /**
+ * Pointer to the first free element in the chain.
+ */
+ Slice *available;
+
+ size_t CalcAllocationSize() const {
+ return n_max * sizeof(Slice);
+ }
+
+public:
+ SliceBuffer(unsigned _count)
+ :n_max(_count), n_initialized(0), n_allocated(0),
+ data((Slice *)HugeAllocate(CalcAllocationSize())),
+ available(nullptr) {
+ assert(n_max > 0);
+ }
+
+ ~SliceBuffer() {
+ /* all slices must be freed explicitly, and this
+ assertion checks for leaks */
+ assert(n_allocated == 0);
+
+ HugeFree(data, CalcAllocationSize());
+ }
+
+ SliceBuffer(const SliceBuffer &other) = delete;
+ SliceBuffer &operator=(const SliceBuffer &other) = delete;
+
+ /**
+ * @return true if buffer allocation (by the constructor) has failed
+ */
+ bool IsOOM() {
+ return data == nullptr;
+ }
+
+ unsigned GetCapacity() const {
+ return n_max;
+ }
+
+ bool IsEmpty() const {
+ return n_allocated == 0;
+ }
+
+ bool IsFull() const {
+ return n_allocated == n_max;
+ }
+
+ template<typename... Args>
+ T *Allocate(Args&&... args) {
+ assert(n_initialized <= n_max);
+ assert(n_allocated <= n_initialized);
+
+ if (available == nullptr) {
+ if (n_initialized == n_max) {
+ /* out of (internal) memory, buffer is full */
+ assert(n_allocated == n_max);
+ return nullptr;
+ }
+
+ available = &data[n_initialized++];
+ available->next = nullptr;
+ }
+
+ /* allocate a slice */
+ T *value = &available->value;
+ available = available->next;
+ ++n_allocated;
+
+ /* construct the object */
+ return ::new((void *)value) T(std::forward<Args>(args)...);
+ }
+
+ void Free(T *value) {
+ assert(n_initialized <= n_max);
+ assert(n_allocated > 0);
+ assert(n_allocated <= n_initialized);
+
+ Slice *slice = reinterpret_cast<Slice *>(value);
+ assert(slice >= data && slice < data + n_max);
+
+ /* destruct the object */
+ value->~T();
+
+ /* insert the slice in the "available" linked list */
+ slice->next = available;
+ available = slice;
+ --n_allocated;
+
+ /* give memory back to the kernel when the last slice
+ was freed */
+ if (n_allocated == 0) {
+ HugeDiscard(data, CalcAllocationSize());
+ n_initialized = 0;
+ available = nullptr;
+ }
+ }
+};
+
+#endif
diff --git a/src/util/StringUtil.cxx b/src/util/StringUtil.cxx
new file mode 100644
index 000000000..87d032735
--- /dev/null
+++ b/src/util/StringUtil.cxx
@@ -0,0 +1,46 @@
+/*
+ * 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 "StringUtil.hxx"
+
+#include <glib.h>
+
+#include <assert.h>
+
+const char *
+strchug_fast_c(const char *p)
+{
+ while (*p != 0 && g_ascii_isspace(*p))
+ ++p;
+
+ return p;
+}
+
+bool
+string_array_contains(const char *const* haystack, const char *needle)
+{
+ assert(haystack != nullptr);
+ assert(needle != nullptr);
+
+ for (; *haystack != nullptr; ++haystack)
+ if (g_ascii_strcasecmp(*haystack, needle) == 0)
+ return true;
+
+ return false;
+}
diff --git a/src/util/StringUtil.hxx b/src/util/StringUtil.hxx
new file mode 100644
index 000000000..72d613798
--- /dev/null
+++ b/src/util/StringUtil.hxx
@@ -0,0 +1,80 @@
+/*
+ * 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_STRING_UTIL_HXX
+#define MPD_STRING_UTIL_HXX
+
+#include "gcc.h"
+
+/**
+ * Remove the "const" attribute from a string pointer. This is a
+ * dirty hack, don't use it unless you know what you're doing!
+ */
+gcc_const
+static inline char *
+deconst_string(const char *p)
+{
+#ifdef __cplusplus
+ return const_cast<char *>(p);
+#else
+ union {
+ const char *in;
+ char *out;
+ } u = {
+ .in = p,
+ };
+
+ return u.out;
+#endif
+}
+
+/**
+ * Returns a pointer to the first non-whitespace character in the
+ * string, or to the end of the string.
+ *
+ * This is a faster version of g_strchug(), because it does not move
+ * data.
+ */
+gcc_pure
+const char *
+strchug_fast_c(const char *p);
+
+/**
+ * Same as strchug_fast_c(), but works with a writable pointer.
+ */
+gcc_pure
+static inline char *
+strchug_fast(char *p)
+{
+ return deconst_string(strchug_fast_c(p));
+}
+
+/**
+ * Checks whether a string array contains the specified string.
+ *
+ * @param haystack a NULL terminated list of strings
+ * @param needle the string to search for; the comparison is
+ * case-insensitive for ASCII characters
+ * @return true if found
+ */
+gcc_pure
+bool
+string_array_contains(const char *const* haystack, const char *needle);
+
+#endif
diff --git a/src/util/Tokenizer.cxx b/src/util/Tokenizer.cxx
new file mode 100644
index 000000000..37650483f
--- /dev/null
+++ b/src/util/Tokenizer.cxx
@@ -0,0 +1,201 @@
+/*
+ * 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 "Tokenizer.hxx"
+#include "StringUtil.hxx"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+
+G_GNUC_CONST
+static GQuark
+tokenizer_quark(void)
+{
+ return g_quark_from_static_string("tokenizer");
+}
+
+static inline bool
+valid_word_first_char(char ch)
+{
+ return g_ascii_isalpha(ch);
+}
+
+static inline bool
+valid_word_char(char ch)
+{
+ return g_ascii_isalnum(ch) || ch == '_';
+}
+
+char *
+Tokenizer::NextWord(GError **error_r)
+{
+ char *const word = input;
+
+ if (*input == 0)
+ return nullptr;
+
+ /* check the first character */
+
+ if (!valid_word_first_char(*input)) {
+ g_set_error(error_r, tokenizer_quark(), 0,
+ "Letter expected");
+ return nullptr;
+ }
+
+ /* now iterate over the other characters until we find a
+ whitespace or end-of-string */
+
+ while (*++input != 0) {
+ if (g_ascii_isspace(*input)) {
+ /* a whitespace: the word ends here */
+ *input = 0;
+ /* skip all following spaces, too */
+ input = strchug_fast(input + 1);
+ break;
+ }
+
+ if (!valid_word_char(*input)) {
+ g_set_error(error_r, tokenizer_quark(), 0,
+ "Invalid word character");
+ return nullptr;
+ }
+ }
+
+ /* end of string: the string is already null-terminated
+ here */
+
+ return word;
+}
+
+static inline bool
+valid_unquoted_char(char ch)
+{
+ return (unsigned char)ch > 0x20 && ch != '"' && ch != '\'';
+}
+
+char *
+Tokenizer::NextUnquoted(GError **error_r)
+{
+ char *const word = input;
+
+ if (*input == 0)
+ return nullptr;
+
+ /* check the first character */
+
+ if (!valid_unquoted_char(*input)) {
+ g_set_error(error_r, tokenizer_quark(), 0,
+ "Invalid unquoted character");
+ return nullptr;
+ }
+
+ /* now iterate over the other characters until we find a
+ whitespace or end-of-string */
+
+ while (*++input != 0) {
+ if (g_ascii_isspace(*input)) {
+ /* a whitespace: the word ends here */
+ *input = 0;
+ /* skip all following spaces, too */
+ input = strchug_fast(input + 1);
+ break;
+ }
+
+ if (!valid_unquoted_char(*input)) {
+ g_set_error(error_r, tokenizer_quark(), 0,
+ "Invalid unquoted character");
+ return nullptr;
+ }
+ }
+
+ /* end of string: the string is already null-terminated
+ here */
+
+ return word;
+}
+
+char *
+Tokenizer::NextString(GError **error_r)
+{
+ char *const word = input, *dest = input;
+
+ if (*input == 0)
+ /* end of line */
+ return nullptr;
+
+ /* check for the opening " */
+
+ if (*input != '"') {
+ g_set_error(error_r, tokenizer_quark(), 0,
+ "'\"' expected");
+ return nullptr;
+ }
+
+ ++input;
+
+ /* copy all characters */
+
+ while (*input != '"') {
+ if (*input == '\\')
+ /* the backslash escapes the following
+ character */
+ ++input;
+
+ if (*input == 0) {
+ /* return input-1 so the caller can see the
+ difference between "end of line" and
+ "error" */
+ --input;
+ g_set_error(error_r, tokenizer_quark(), 0,
+ "Missing closing '\"'");
+ return nullptr;
+ }
+
+ /* copy one character */
+ *dest++ = *input++;
+ }
+
+ /* the following character must be a whitespace (or end of
+ line) */
+
+ ++input;
+ if (*input != 0 && !g_ascii_isspace(*input)) {
+ g_set_error(error_r, tokenizer_quark(), 0,
+ "Space expected after closing '\"'");
+ return nullptr;
+ }
+
+ /* finish the string and return it */
+
+ *dest = 0;
+ input = strchug_fast(input);
+ return word;
+}
+
+char *
+Tokenizer::NextParam(GError **error_r)
+{
+ if (*input == '"')
+ return NextString(error_r);
+ else
+ return NextUnquoted(error_r);
+}
diff --git a/src/util/Tokenizer.hxx b/src/util/Tokenizer.hxx
new file mode 100644
index 000000000..da45348d4
--- /dev/null
+++ b/src/util/Tokenizer.hxx
@@ -0,0 +1,99 @@
+/*
+ * 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_TOKENIZER_HXX
+#define MPD_TOKENIZER_HXX
+
+#include "gerror.h"
+
+class Tokenizer {
+ char *input;
+
+public:
+ /**
+ * @param _input the input string; the contents will be
+ * modified by this class
+ */
+ constexpr Tokenizer(char *_input):input(_input) {}
+
+ Tokenizer(const Tokenizer &) = delete;
+ Tokenizer &operator=(const Tokenizer &) = delete;
+
+ char *Rest() {
+ return input;
+ }
+
+ char CurrentChar() const {
+ return *input;
+ }
+
+ bool IsEnd() const {
+ return CurrentChar() == 0;
+ }
+
+ /**
+ * Reads the next word.
+ *
+ * @param error_r if this function returns nullptr and
+ * **input_p!=0, it optionally provides a GError object in
+ * this argument
+ * @return a pointer to the null-terminated word, or nullptr
+ * on error or end of line
+ */
+ char *NextWord(GError **error_r);
+
+ /**
+ * Reads the next unquoted word from the input string.
+ *
+ * @param error_r if this function returns nullptr and **input_p!=0, it
+ * optionally provides a GError object in this argument
+ * @return a pointer to the null-terminated word, or nullptr
+ * on error or end of line
+ */
+ char *NextUnquoted(GError **error_r);
+
+ /**
+ * Reads the next quoted string from the input string. A backslash
+ * escapes the following character. This function modifies the input
+ * string.
+ *
+ * @param input_p the input string; this function returns a pointer to
+ * the first non-whitespace character of the following token
+ * @param error_r if this function returns nullptr and **input_p!=0, it
+ * optionally provides a GError object in this argument
+ * @return a pointer to the null-terminated string, or nullptr on error
+ * or end of line
+ */
+ char *NextString(GError **error_r);
+
+ /**
+ * Reads the next unquoted word or quoted string from the
+ * input. This is a wrapper for NextUnquoted() and
+ * NextString().
+ *
+ * @param error_r if this function returns nullptr and
+ * **input_p!=0, it optionally provides a GError object in
+ * this argument
+ * @return a pointer to the null-terminated string, or nullptr
+ * on error or end of line
+ */
+ char *NextParam(GError **error_r);
+};
+
+#endif
diff --git a/src/util/UriUtil.cxx b/src/util/UriUtil.cxx
new file mode 100644
index 000000000..4b0cec11b
--- /dev/null
+++ b/src/util/UriUtil.cxx
@@ -0,0 +1,113 @@
+/*
+ * 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 "UriUtil.hxx"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+
+bool uri_has_scheme(const char *uri)
+{
+ return strstr(uri, "://") != nullptr;
+}
+
+/* suffixes should be ascii only characters */
+const char *
+uri_get_suffix(const char *uri)
+{
+ const char *suffix = strrchr(uri, '.');
+ if (suffix == nullptr)
+ return nullptr;
+
+ ++suffix;
+
+ if (strpbrk(suffix, "/\\") != nullptr)
+ return nullptr;
+
+ return suffix;
+}
+
+static const char *
+verify_uri_segment(const char *p)
+{
+ const char *q;
+
+ unsigned dots = 0;
+ while (*p == '.') {
+ ++p;
+ ++dots;
+ }
+
+ if (dots <= 2 && (*p == 0 || *p == '/'))
+ return nullptr;
+
+ q = strchr(p + 1, '/');
+ return q != nullptr ? q : "";
+}
+
+bool
+uri_safe_local(const char *uri)
+{
+ while (true) {
+ uri = verify_uri_segment(uri);
+ if (uri == nullptr)
+ return false;
+
+ if (*uri == 0)
+ return true;
+
+ assert(*uri == '/');
+
+ ++uri;
+ }
+}
+
+char *
+uri_remove_auth(const char *uri)
+{
+ const char *auth, *slash, *at;
+ char *p;
+
+ if (strncmp(uri, "http://", 7) == 0)
+ auth = uri + 7;
+ else if (strncmp(uri, "https://", 8) == 0)
+ auth = uri + 8;
+ else
+ /* unrecognized URI */
+ return nullptr;
+
+ slash = strchr(auth, '/');
+ if (slash == nullptr)
+ slash = auth + strlen(auth);
+
+ at = (const char *)memchr(auth, '@', slash - auth);
+ if (at == nullptr)
+ /* no auth info present, do nothing */
+ return nullptr;
+
+ /* duplicate the full URI and then delete the auth
+ information */
+ p = g_strdup(uri);
+ memmove(p + (auth - uri), p + (at + 1 - uri),
+ strlen(at));
+
+ return p;
+}
diff --git a/src/util/UriUtil.hxx b/src/util/UriUtil.hxx
new file mode 100644
index 000000000..1d288ca1d
--- /dev/null
+++ b/src/util/UriUtil.hxx
@@ -0,0 +1,58 @@
+/*
+ * 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_URI_UTIL_HXX
+#define MPD_URI_UTIL_HXX
+
+#include "gcc.h"
+
+/**
+ * Checks whether the specified URI has a scheme in the form
+ * "scheme://".
+ */
+gcc_pure
+bool uri_has_scheme(const char *uri);
+
+gcc_pure
+const char *
+uri_get_suffix(const char *uri);
+
+/**
+ * Returns true if this is a safe "local" URI:
+ *
+ * - non-empty
+ * - does not begin or end with a slash
+ * - no double slashes
+ * - no path component begins with a dot
+ */
+gcc_pure
+bool
+uri_safe_local(const char *uri);
+
+/**
+ * Removes HTTP username and password from the URI. This may be
+ * useful for displaying an URI without disclosing secrets. Returns
+ * NULL if nothing needs to be removed, or if the URI is not
+ * recognized.
+ */
+gcc_malloc
+char *
+uri_remove_auth(const char *uri);
+
+#endif
diff --git a/src/util/bit_reverse.h b/src/util/bit_reverse.h
index e44693b1d..54cb789bb 100644
--- a/src/util/bit_reverse.h
+++ b/src/util/bit_reverse.h
@@ -20,12 +20,13 @@
#ifndef MPD_BIT_REVERSE_H
#define MPD_BIT_REVERSE_H
-#include <glib.h>
+#include "gcc.h"
+
#include <stdint.h>
extern const uint8_t bit_reverse_table[256];
-G_GNUC_CONST
+gcc_const
static inline uint8_t
bit_reverse(uint8_t x)
{
diff --git a/src/util/fifo_buffer.c b/src/util/fifo_buffer.c
new file mode 100644
index 000000000..162ddf946
--- /dev/null
+++ b/src/util/fifo_buffer.c
@@ -0,0 +1,218 @@
+/*
+ * 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
new file mode 100644
index 000000000..ccea97d86
--- /dev/null
+++ b/src/util/fifo_buffer.h
@@ -0,0 +1,164 @@
+/*
+ * 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/growing_fifo.c b/src/util/growing_fifo.c
index 88431f60e..88431f60e 100644
--- a/src/growing_fifo.c
+++ b/src/util/growing_fifo.c
diff --git a/src/growing_fifo.h b/src/util/growing_fifo.h
index 723c3b3ff..723c3b3ff 100644
--- a/src/growing_fifo.h
+++ b/src/util/growing_fifo.h
diff --git a/src/util/list.h b/src/util/list.h
index fdab47675..73d99befa 100644
--- a/src/util/list.h
+++ b/src/util/list.h
@@ -25,8 +25,6 @@
#ifndef _LINUX_LIST_H
#define _LINUX_LIST_H
-#include <glib.h>
-
#ifdef __clang__
/* allow typeof() */
#pragma GCC diagnostic ignored "-Wlanguage-extension-token"
@@ -40,15 +38,15 @@
*
*/
#define container_of(ptr, type, member) \
- (&G_STRUCT_MEMBER(type, ptr, -G_STRUCT_OFFSET(type, member)))
+ ((type *)((uint8_t *)ptr - offsetof(type, member)))
/*
* These are non-NULL pointers that will result in page faults
* under normal circumstances, used to verify that nobody uses
* non-initialized list entries.
*/
-#define LIST_POISON1 ((void *) 0x00100100)
-#define LIST_POISON2 ((void *) 0x00200200)
+#define LIST_POISON1 ((struct list_head *)(void *) 0x00100100)
+#define LIST_POISON2 ((struct list_head *)(void *) 0x00200200)
/*
* Simple doubly linked list implementation.
@@ -82,46 +80,47 @@ static inline void INIT_LIST_HEAD(struct list_head *list)
* the prev/next entries already!
*/
#ifndef CONFIG_DEBUG_LIST
-static inline void __list_add(struct list_head *new,
+static inline void __list_add(struct list_head *new_item,
struct list_head *prev,
struct list_head *next)
{
- next->prev = new;
- new->next = next;
- new->prev = prev;
- prev->next = new;
+ next->prev = new_item;
+ new_item->next = next;
+ new_item->prev = prev;
+ prev->next = new_item;
}
#else
-extern void __list_add(struct list_head *new,
- struct list_head *prev,
- struct list_head *next);
+extern void __list_add(struct list_head *new_item,
+ struct list_head *prev,
+ struct list_head *next);
#endif
/**
* list_add - add a new entry
- * @new: new entry to be added
+ * @new_item: new entry to be added
* @head: list head to add it after
*
* Insert a new entry after the specified head.
* This is good for implementing stacks.
*/
-static inline void list_add(struct list_head *new, struct list_head *head)
+static inline void list_add(struct list_head *new_item, struct list_head *head)
{
- __list_add(new, head, head->next);
+ __list_add(new_item, head, head->next);
}
/**
* list_add_tail - add a new entry
- * @new: new entry to be added
+ * @new_item: new entry to be added
* @head: list head to add it before
*
* Insert a new entry before the specified head.
* This is useful for implementing queues.
*/
-static inline void list_add_tail(struct list_head *new, struct list_head *head)
+static inline void
+list_add_tail(struct list_head *new_item, struct list_head *head)
{
- __list_add(new, head->prev, head);
+ __list_add(new_item, head->prev, head);
}
/*
@@ -163,23 +162,23 @@ extern void list_del(struct list_head *entry);
/**
* list_replace - replace old entry by new one
* @old : the element to be replaced
- * @new : the new element to insert
+ * @new_item : the new element to insert
*
* If @old was empty, it will be overwritten.
*/
static inline void list_replace(struct list_head *old,
- struct list_head *new)
+ struct list_head *new_item)
{
- new->next = old->next;
- new->next->prev = new;
- new->prev = old->prev;
- new->prev->next = new;
+ new_item->next = old->next;
+ new_item->next->prev = new_item;
+ new_item->prev = old->prev;
+ new_item->prev->next = new_item;
}
static inline void list_replace_init(struct list_head *old,
- struct list_head *new)
+ struct list_head *new_item)
{
- list_replace(old, new);
+ list_replace(old, new_item);
INIT_LIST_HEAD(old);
}
diff --git a/src/utils.c b/src/utils.c
deleted file mode 100644
index a2de3212e..000000000
--- a/src/utils.c
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "utils.h"
-#include "glib_compat.h"
-#include "conf.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-#include <sys/types.h>
-#include <fcntl.h>
-#include <errno.h>
-
-#ifndef WIN32
-#include <pwd.h>
-#endif
-
-#if HAVE_IPV6 && WIN32
-#include <winsock2.h>
-#endif
-
-#if HAVE_IPV6 && ! WIN32
-#include <sys/socket.h>
-#endif
-
-#ifdef WIN32
-#include <windows.h>
-#endif
-
-G_GNUC_CONST
-static inline GQuark
-parse_path_quark(void)
-{
- return g_quark_from_static_string("path");
-}
-
-char *
-parsePath(const char *path, G_GNUC_UNUSED GError **error_r)
-{
- assert(path != NULL);
- assert(error_r == NULL || *error_r == NULL);
-
-#ifndef WIN32
- if (!g_path_is_absolute(path) && path[0] != '~') {
- g_set_error(error_r, parse_path_quark(), 0,
- "not an absolute path: %s", path);
- return NULL;
- } else if (path[0] == '~') {
- const char *home;
-
- if (path[1] == '/' || path[1] == '\0') {
- const char *user = config_get_string(CONF_USER, NULL);
- if (user != NULL) {
- struct passwd *passwd = getpwnam(user);
- if (!passwd) {
- g_set_error(error_r, parse_path_quark(), 0,
- "no such user: %s", user);
- return NULL;
- }
-
- home = passwd->pw_dir;
- } else {
- home = g_get_home_dir();
- if (home == NULL) {
- g_set_error_literal(error_r, parse_path_quark(), 0,
- "problems getting home "
- "for current user");
- return NULL;
- }
- }
-
- ++path;
- } else {
- ++path;
-
- const char *slash = strchr(path, '/');
- char *user = slash != NULL
- ? g_strndup(path, slash - path)
- : g_strdup(path);
-
- struct passwd *passwd = getpwnam(user);
- if (!passwd) {
- g_set_error(error_r, parse_path_quark(), 0,
- "no such user: %s", user);
- g_free(user);
- return NULL;
- }
-
- g_free(user);
-
- home = passwd->pw_dir;
- path = slash;
- }
-
- return g_strconcat(home, path, NULL);
- } else {
-#endif
- return g_strdup(path);
-#ifndef WIN32
- }
-#endif
-}
diff --git a/src/utils.h b/src/utils.h
deleted file mode 100644
index f8d6657f2..000000000
--- a/src/utils.h
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * 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_UTILS_H
-#define MPD_UTILS_H
-
-#include <glib.h>
-#include <stdbool.h>
-
-#ifndef assert_static
-/* Compile time assertion developed by Ralf Holly */
-/* http://pera-software.com/articles/compile-time-assertions.pdf */
-#define assert_static(e) \
- do { \
- enum { assert_static__ = 1/(e) }; \
- } while (0)
-#endif /* !assert_static */
-
-char *
-parsePath(const char *path, GError **error_r);
-
-#endif
diff --git a/src/volume.c b/src/volume.c
deleted file mode 100644
index d3ce47dd4..000000000
--- a/src/volume.c
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "volume.h"
-#include "conf.h"
-#include "idle.h"
-#include "pcm_volume.h"
-#include "output_all.h"
-#include "mixer_control.h"
-#include "mixer_all.h"
-#include "mixer_type.h"
-#include "event_pipe.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <math.h>
-#include <string.h>
-#include <stdlib.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "volume"
-
-#define SW_VOLUME_STATE "sw_volume: "
-
-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;
-
-/**
- * Handler for #PIPE_EVENT_MIXER.
- */
-static void
-mixer_event_callback(void)
-{
- /* flush the hardware volume cache */
- last_hardware_volume = -1;
-
- /* notify clients */
- idle_add(IDLE_MIXER);
-}
-
-void volume_finish(void)
-{
- g_timer_destroy(hardware_volume_timer);
-}
-
-void volume_init(void)
-{
- hardware_volume_timer = g_timer_new();
-
- event_pipe_register(PIPE_EVENT_MIXER, mixer_event_callback);
-}
-
-int volume_level_get(void)
-{
- assert(hardware_volume_timer != NULL);
-
- if (last_hardware_volume >= 0 &&
- g_timer_elapsed(hardware_volume_timer, NULL) < 1.0)
- /* 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;
-}
-
-static bool software_volume_change(unsigned volume)
-{
- assert(volume <= 100);
-
- volume_software_set = volume;
- mixer_all_set_software_volume(volume);
-
- return true;
-}
-
-static bool hardware_volume_change(unsigned volume)
-{
- /* reset the cache */
- last_hardware_volume = -1;
-
- return mixer_all_set_volume(volume);
-}
-
-bool volume_level_change(unsigned volume)
-{
- assert(volume <= 100);
-
- volume_software_set = volume;
-
- idle_add(IDLE_MIXER);
-
- return hardware_volume_change(volume);
-}
-
-bool
-read_sw_volume_state(const char *line)
-{
- char *end = NULL;
- long int sv;
-
- if (!g_str_has_prefix(line, SW_VOLUME_STATE))
- return false;
-
- line += sizeof(SW_VOLUME_STATE) - 1;
- sv = strtol(line, &end, 10);
- if (*end == 0 && sv >= 0 && sv <= 100)
- software_volume_change(sv);
- else
- g_warning("Can't parse software volume: %s\n", line);
- return true;
-}
-
-void save_sw_volume_state(FILE *fp)
-{
- fprintf(fp, SW_VOLUME_STATE "%u\n", volume_software_set);
-}
-
-unsigned
-sw_volume_state_get_hash(void)
-{
- return volume_software_set;
-}
diff --git a/src/volume.h b/src/volume.h
deleted file mode 100644
index b08899a84..000000000
--- a/src/volume.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * 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_VOLUME_H
-#define MPD_VOLUME_H
-
-#include <stdbool.h>
-#include <stdio.h>
-
-void volume_init(void);
-
-void volume_finish(void);
-
-int volume_level_get(void);
-
-bool volume_level_change(unsigned volume);
-
-bool
-read_sw_volume_state(const char *line);
-
-void save_sw_volume_state(FILE *fp);
-
-/**
- * Generates a hash number for the current state of the software
- * volume control. This is used by timer_save_state_file() to
- * determine whether the state has changed and the state file should
- * be saved.
- */
-unsigned
-sw_volume_state_get_hash(void);
-
-#endif
diff --git a/src/zeroconf-avahi.c b/src/zeroconf-avahi.c
deleted file mode 100644
index f2cc5359b..000000000
--- a/src/zeroconf-avahi.c
+++ /dev/null
@@ -1,267 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "zeroconf-internal.h"
-#include "listen.h"
-#include "mpd_error.h"
-
-#include <glib.h>
-
-#include <avahi-client/client.h>
-#include <avahi-client/publish.h>
-
-#include <avahi-common/alternative.h>
-#include <avahi-common/domain.h>
-#include <avahi-common/malloc.h>
-#include <avahi-common/error.h>
-
-#include <avahi-glib/glib-watch.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "avahi"
-
-static char *avahiName;
-static int avahiRunning;
-static AvahiGLibPoll *avahi_glib_poll;
-static const AvahiPoll *avahi_poll;
-static AvahiClient *avahiClient;
-static AvahiEntryGroup *avahiGroup;
-
-static void avahiRegisterService(AvahiClient * c);
-
-/* Callback when the EntryGroup changes state */
-static void avahiGroupCallback(AvahiEntryGroup * g,
- AvahiEntryGroupState state,
- G_GNUC_UNUSED void *userdata)
-{
- char *n;
- assert(g);
-
- g_debug("Service group changed to state %d", state);
-
- switch (state) {
- case AVAHI_ENTRY_GROUP_ESTABLISHED:
- /* The entry group has been established successfully */
- g_message("Service '%s' successfully established.",
- avahiName);
- break;
-
- case AVAHI_ENTRY_GROUP_COLLISION:
- /* A service name collision happened. Let's pick a new name */
- n = avahi_alternative_service_name(avahiName);
- avahi_free(avahiName);
- avahiName = n;
-
- g_message("Service name collision, renaming service to '%s'",
- avahiName);
-
- /* And recreate the services */
- avahiRegisterService(avahi_entry_group_get_client(g));
- break;
-
- case AVAHI_ENTRY_GROUP_FAILURE:
- g_warning("Entry group failure: %s",
- avahi_strerror(avahi_client_errno
- (avahi_entry_group_get_client(g))));
- /* Some kind of failure happened while we were registering our services */
- avahiRunning = 0;
- break;
-
- case AVAHI_ENTRY_GROUP_UNCOMMITED:
- g_debug("Service group is UNCOMMITED");
- break;
- case AVAHI_ENTRY_GROUP_REGISTERING:
- g_debug("Service group is REGISTERING");
- }
-}
-
-/* Registers a new service with avahi */
-static void avahiRegisterService(AvahiClient * c)
-{
- int ret;
- assert(c);
- g_debug("Registering service %s/%s", SERVICE_TYPE, avahiName);
-
- /* If this is the first time we're called,
- * let's create a new entry group */
- if (!avahiGroup) {
- avahiGroup = avahi_entry_group_new(c, avahiGroupCallback, NULL);
- if (!avahiGroup) {
- g_warning("Failed to create avahi EntryGroup: %s",
- avahi_strerror(avahi_client_errno(c)));
- goto fail;
- }
- }
-
- /* Add the service */
- /* TODO: This currently binds to ALL interfaces.
- * We could maybe add a service per actual bound interface,
- * if that's better. */
- ret = avahi_entry_group_add_service(avahiGroup,
- AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
- 0, avahiName, SERVICE_TYPE, NULL,
- NULL, listen_port, NULL);
- if (ret < 0) {
- g_warning("Failed to add service %s: %s", SERVICE_TYPE,
- avahi_strerror(ret));
- goto fail;
- }
-
- /* Tell the server to register the service group */
- ret = avahi_entry_group_commit(avahiGroup);
- if (ret < 0) {
- g_warning("Failed to commit service group: %s",
- avahi_strerror(ret));
- goto fail;
- }
- return;
-
-fail:
- avahiRunning = 0;
-}
-
-/* Callback when avahi changes state */
-static void avahiClientCallback(AvahiClient * c, AvahiClientState state,
- G_GNUC_UNUSED void *userdata)
-{
- int reason;
- assert(c);
-
- /* Called whenever the client or server state changes */
- g_debug("Client changed to state %d", state);
-
- switch (state) {
- case AVAHI_CLIENT_S_RUNNING:
- g_debug("Client is RUNNING");
-
- /* The server has startup successfully and registered its host
- * name on the network, so it's time to create our services */
- if (!avahiGroup)
- avahiRegisterService(c);
- break;
-
- case AVAHI_CLIENT_FAILURE:
- reason = avahi_client_errno(c);
- if (reason == AVAHI_ERR_DISCONNECTED) {
- g_message("Client Disconnected, will reconnect shortly");
- if (avahiGroup) {
- avahi_entry_group_free(avahiGroup);
- avahiGroup = NULL;
- }
- if (avahiClient)
- avahi_client_free(avahiClient);
- avahiClient =
- avahi_client_new(avahi_poll,
- AVAHI_CLIENT_NO_FAIL,
- avahiClientCallback, NULL,
- &reason);
- if (!avahiClient) {
- g_warning("Could not reconnect: %s",
- avahi_strerror(reason));
- avahiRunning = 0;
- }
- } else {
- g_warning("Client failure: %s (terminal)",
- avahi_strerror(reason));
- avahiRunning = 0;
- }
- break;
-
- case AVAHI_CLIENT_S_COLLISION:
- g_debug("Client is COLLISION");
- /* Let's drop our registered services. When the server is back
- * in AVAHI_SERVER_RUNNING state we will register them
- * again with the new host name. */
- if (avahiGroup) {
- g_debug("Resetting group");
- avahi_entry_group_reset(avahiGroup);
- }
-
- case AVAHI_CLIENT_S_REGISTERING:
- g_debug("Client is REGISTERING");
- /* The server records are now being established. This
- * might be caused by a host name change. We need to wait
- * for our own records to register until the host name is
- * properly esatblished. */
-
- if (avahiGroup) {
- g_debug("Resetting group");
- avahi_entry_group_reset(avahiGroup);
- }
-
- break;
-
- case AVAHI_CLIENT_CONNECTING:
- g_debug("Client is CONNECTING");
- }
-}
-
-void init_avahi(const char *serviceName)
-{
- int error;
- g_debug("Initializing interface");
-
- if (!avahi_is_valid_service_name(serviceName))
- MPD_ERROR("Invalid zeroconf_name \"%s\"", serviceName);
-
- avahiName = avahi_strdup(serviceName);
-
- avahiRunning = 1;
-
- avahi_glib_poll = avahi_glib_poll_new(NULL, G_PRIORITY_DEFAULT);
- avahi_poll = avahi_glib_poll_get(avahi_glib_poll);
-
- avahiClient = avahi_client_new(avahi_poll, AVAHI_CLIENT_NO_FAIL,
- avahiClientCallback, NULL, &error);
-
- if (!avahiClient) {
- g_warning("Failed to create client: %s",
- avahi_strerror(error));
- goto fail;
- }
-
- return;
-
-fail:
- avahi_finish();
-}
-
-void avahi_finish(void)
-{
- g_debug("Shutting down interface");
-
- if (avahiGroup) {
- avahi_entry_group_free(avahiGroup);
- avahiGroup = NULL;
- }
-
- if (avahiClient) {
- avahi_client_free(avahiClient);
- avahiClient = NULL;
- }
-
- if (avahi_glib_poll != NULL) {
- avahi_glib_poll_free(avahi_glib_poll);
- avahi_glib_poll = NULL;
- }
-
- avahi_free(avahiName);
- avahiName = NULL;
-}
diff --git a/src/zeroconf-bonjour.c b/src/zeroconf-bonjour.c
deleted file mode 100644
index 0f216aade..000000000
--- a/src/zeroconf-bonjour.c
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "zeroconf-internal.h"
-#include "listen.h"
-
-#include <glib.h>
-
-#include <dns_sd.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "bonjour"
-
-static DNSServiceRef dnsReference;
-static GIOChannel *bonjour_channel;
-
-static void
-dnsRegisterCallback(G_GNUC_UNUSED DNSServiceRef sdRef,
- G_GNUC_UNUSED DNSServiceFlags flags,
- DNSServiceErrorType errorCode, const char *name,
- G_GNUC_UNUSED const char *regtype,
- G_GNUC_UNUSED const char *domain,
- G_GNUC_UNUSED void *context)
-{
- if (errorCode != kDNSServiceErr_NoError) {
- g_warning("Failed to register zeroconf service.");
-
- bonjour_finish();
- } else {
- g_debug("Registered zeroconf service with name '%s'", name);
- }
-}
-
-static gboolean
-bonjour_channel_event(G_GNUC_UNUSED GIOChannel *source,
- G_GNUC_UNUSED GIOCondition condition,
- G_GNUC_UNUSED gpointer data)
-{
- DNSServiceProcessResult(dnsReference);
-
- return dnsReference != NULL;
-}
-
-void init_zeroconf_osx(const char *serviceName)
-{
- DNSServiceErrorType error = DNSServiceRegister(&dnsReference,
- 0, 0, serviceName,
- SERVICE_TYPE, NULL, NULL,
- g_htons(listen_port), 0,
- NULL,
- dnsRegisterCallback,
- NULL);
-
- if (error != kDNSServiceErr_NoError) {
- g_warning("Failed to register zeroconf service.");
-
- if (dnsReference) {
- DNSServiceRefDeallocate(dnsReference);
- dnsReference = NULL;
- }
- return;
- }
-
- bonjour_channel = g_io_channel_unix_new(DNSServiceRefSockFD(dnsReference));
- g_io_add_watch(bonjour_channel, G_IO_IN, bonjour_channel_event, NULL);
-}
-
-void bonjour_finish(void)
-{
- if (bonjour_channel != NULL) {
- g_io_channel_unref(bonjour_channel);
- bonjour_channel = NULL;
- }
-
- if (dnsReference != NULL) {
- DNSServiceRefDeallocate(dnsReference);
- dnsReference = NULL;
- g_debug("Deregistered Zeroconf service.");
- }
-}
diff --git a/src/zeroconf-internal.h b/src/zeroconf-internal.h
deleted file mode 100644
index 983e5c556..000000000
--- a/src/zeroconf-internal.h
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * 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 ZEROCONF_INTERNAL_H
-#define ZEROCONF_INTERNAL_H
-
-/* The dns-sd service type qualifier to publish */
-#define SERVICE_TYPE "_mpd._tcp"
-
-void init_avahi(const char *service_name);
-
-void avahi_finish(void);
-
-void init_zeroconf_osx(const char *service_name);
-
-void bonjour_finish(void);
-
-#endif
diff --git a/src/zeroconf.c b/src/zeroconf.c
deleted file mode 100644
index 4a399e4a2..000000000
--- a/src/zeroconf.c
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "zeroconf.h"
-#include "zeroconf-internal.h"
-#include "conf.h"
-#include "listen.h"
-
-#include <glib.h>
-
-/* The default service name to publish
- * (overridden by 'zeroconf_name' config parameter)
- */
-#define SERVICE_NAME "Music Player"
-
-#define DEFAULT_ZEROCONF_ENABLED 1
-
-static int zeroconfEnabled;
-
-void initZeroconf(void)
-{
- const char *serviceName;
-
- zeroconfEnabled = config_get_bool(CONF_ZEROCONF_ENABLED,
- DEFAULT_ZEROCONF_ENABLED);
- if (!zeroconfEnabled)
- return;
-
- if (listen_port <= 0) {
- g_warning("No global port, disabling zeroconf");
- zeroconfEnabled = false;
- return;
- }
-
- serviceName = config_get_string(CONF_ZEROCONF_NAME, SERVICE_NAME);
-
-#ifdef HAVE_AVAHI
- init_avahi(serviceName);
-#endif
-
-#ifdef HAVE_BONJOUR
- init_zeroconf_osx(serviceName);
-#endif
-}
-
-void finishZeroconf(void)
-{
- if (!zeroconfEnabled)
- return;
-
-#ifdef HAVE_AVAHI
- avahi_finish();
-#endif /* HAVE_AVAHI */
-
-#ifdef HAVE_BONJOUR
- bonjour_finish();
-#endif
-}
diff --git a/src/zeroconf.h b/src/zeroconf.h
deleted file mode 100644
index 8e33a3d89..000000000
--- a/src/zeroconf.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * 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_ZEROCONF_H
-#define MPD_ZEROCONF_H
-
-#include "check.h"
-
-#ifdef HAVE_ZEROCONF
-
-void initZeroconf(void);
-void finishZeroconf(void);
-
-#else /* ! HAVE_ZEROCONF */
-
-static void initZeroconf(void) { }
-static void finishZeroconf(void) { }
-
-#endif /* ! HAVE_ZEROCONF */
-
-#endif
diff --git a/test/DumpDatabase.cxx b/test/DumpDatabase.cxx
new file mode 100644
index 000000000..c6a01fa11
--- /dev/null
+++ b/test/DumpDatabase.cxx
@@ -0,0 +1,150 @@
+/*
+ * 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 "DatabaseRegistry.hxx"
+#include "DatabasePlugin.hxx"
+#include "DatabaseSelection.hxx"
+#include "Directory.hxx"
+#include "Song.hxx"
+#include "PlaylistVector.hxx"
+#include "conf.h"
+#include "Tag.hxx"
+#include "fs/Path.hxx"
+
+#include <iostream>
+using std::cout;
+using std::cerr;
+using std::endl;
+
+#include <stdlib.h>
+
+static void
+my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level,
+ const gchar *message, G_GNUC_UNUSED gpointer user_data)
+{
+ if (log_domain != NULL)
+ g_printerr("%s: %s\n", log_domain, message);
+ else
+ g_printerr("%s\n", message);
+}
+
+static bool
+DumpDirectory(const Directory &directory, GError **)
+{
+ cout << "D " << directory.path << endl;
+ return true;
+}
+
+static bool
+DumpSong(Song &song, GError **)
+{
+ cout << "S " << song.parent->path << "/" << song.uri << endl;
+ return true;
+}
+
+static bool
+DumpPlaylist(const PlaylistInfo &playlist,
+ const Directory &directory, GError **)
+{
+ cout << "P " << directory.path << "/" << playlist.name.c_str() << endl;
+ return true;
+}
+
+int
+main(int argc, char **argv)
+{
+ GError *error = nullptr;
+
+ if (argc != 3) {
+ cerr << "Usage: DumpDatabase CONFIG PLUGIN" << endl;
+ return 1;
+ }
+
+ const Path config_path = Path::FromFS(argv[1]);
+ const char *const plugin_name = argv[2];
+
+ const DatabasePlugin *plugin = GetDatabasePluginByName(plugin_name);
+ if (plugin == NULL) {
+ cerr << "No such database plugin: " << plugin_name << endl;
+ return EXIT_FAILURE;
+ }
+
+ /* initialize GLib */
+
+#if !GLIB_CHECK_VERSION(2,32,0)
+ g_thread_init(nullptr);
+#endif
+
+ g_log_set_default_handler(my_log_func, nullptr);
+
+ /* initialize MPD */
+
+ config_global_init();
+
+ if (!ReadConfigFile(config_path, &error)) {
+ cerr << error->message << endl;
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
+ tag_lib_init();
+
+ /* do it */
+
+ const struct config_param *path = config_get_param(CONF_DB_FILE);
+ config_param param("database", path->line);
+ if (path != nullptr)
+ param.AddBlockParam("path", path->value, path->line);
+
+ Database *db = plugin->create(param, &error);
+
+ if (db == nullptr) {
+ cerr << error->message << endl;
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
+ if (!db->Open(&error)) {
+ delete db;
+ cerr << error->message << endl;
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
+ const DatabaseSelection selection("", true);
+
+ if (!db->Visit(selection, DumpDirectory, DumpSong, DumpPlaylist,
+ &error)) {
+ db->Close();
+ delete db;
+ cerr << error->message << endl;
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
+ db->Close();
+ delete db;
+
+ /* deinitialize everything */
+
+ config_global_finish();
+
+ return EXIT_SUCCESS;
+}
diff --git a/test/FakeReplayGainConfig.cxx b/test/FakeReplayGainConfig.cxx
new file mode 100644
index 000000000..9c2431bf2
--- /dev/null
+++ b/test/FakeReplayGainConfig.cxx
@@ -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.
+ */
+
+#include "config.h"
+#include "replay_gain_config.h"
+
+float replay_gain_preamp = 1.0;
+float replay_gain_missing_preamp = 1.0;
+bool replay_gain_limit = true;
diff --git a/test/FakeSong.cxx b/test/FakeSong.cxx
new file mode 100644
index 000000000..ef7879f1b
--- /dev/null
+++ b/test/FakeSong.cxx
@@ -0,0 +1,33 @@
+/*
+ * 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 "Song.hxx"
+#include "directory.h"
+#include "gcc.h"
+
+#include <stdlib.h>
+
+struct directory detached_root;
+
+Song *
+song_dup_detached(gcc_unused const Song *src)
+{
+ abort();
+}
diff --git a/test/dump_playlist.c b/test/dump_playlist.c
deleted file mode 100644
index 84ac69045..000000000
--- a/test/dump_playlist.c
+++ /dev/null
@@ -1,255 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "io_thread.h"
-#include "input_init.h"
-#include "input_stream.h"
-#include "tag_pool.h"
-#include "tag_save.h"
-#include "conf.h"
-#include "song.h"
-#include "decoder_api.h"
-#include "decoder_list.h"
-#include "playlist_list.h"
-#include "playlist_plugin.h"
-
-#include <glib.h>
-
-#include <unistd.h>
-#include <stdlib.h>
-
-static void
-my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level,
- const gchar *message, G_GNUC_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(G_GNUC_UNUSED struct decoder *decoder,
- G_GNUC_UNUSED const struct audio_format *audio_format,
- G_GNUC_UNUSED bool seekable,
- G_GNUC_UNUSED float total_time)
-{
-}
-
-enum decoder_command
-decoder_get_command(G_GNUC_UNUSED struct decoder *decoder)
-{
- return DECODE_COMMAND_NONE;
-}
-
-void
-decoder_command_finished(G_GNUC_UNUSED struct decoder *decoder)
-{
-}
-
-double
-decoder_seek_where(G_GNUC_UNUSED struct decoder *decoder)
-{
- return 1.0;
-}
-
-void
-decoder_seek_error(G_GNUC_UNUSED struct decoder *decoder)
-{
-}
-
-size_t
-decoder_read(G_GNUC_UNUSED struct decoder *decoder,
- struct input_stream *is,
- void *buffer, size_t length)
-{
- return input_stream_lock_read(is, buffer, length, NULL);
-}
-
-void
-decoder_timestamp(G_GNUC_UNUSED struct decoder *decoder,
- G_GNUC_UNUSED double t)
-{
-}
-
-enum decoder_command
-decoder_data(G_GNUC_UNUSED struct decoder *decoder,
- G_GNUC_UNUSED struct input_stream *is,
- const void *data, size_t datalen,
- G_GNUC_UNUSED uint16_t kbit_rate)
-{
- G_GNUC_UNUSED ssize_t nbytes = write(1, data, datalen);
- return DECODE_COMMAND_NONE;
-}
-
-enum decoder_command
-decoder_tag(G_GNUC_UNUSED struct decoder *decoder,
- G_GNUC_UNUSED struct input_stream *is,
- G_GNUC_UNUSED const struct tag *tag)
-{
- return DECODE_COMMAND_NONE;
-}
-
-float
-decoder_replay_gain(G_GNUC_UNUSED struct decoder *decoder,
- const struct replay_gain_info *replay_gain_info)
-{
- const struct replay_gain_tuple *tuple =
- &replay_gain_info->tuples[REPLAY_GAIN_ALBUM];
- if (replay_gain_tuple_defined(tuple))
- g_printerr("replay_gain[album]: gain=%f peak=%f\n",
- tuple->gain, tuple->peak);
-
- tuple = &replay_gain_info->tuples[REPLAY_GAIN_TRACK];
- if (replay_gain_tuple_defined(tuple))
- g_printerr("replay_gain[track]: gain=%f peak=%f\n",
- tuple->gain, tuple->peak);
-
- return 0.0;
-}
-
-void
-decoder_mixramp(G_GNUC_UNUSED struct decoder *decoder,
- G_GNUC_UNUSED float replay_gain_db,
- char *mixramp_start, char *mixramp_end)
-{
- g_free(mixramp_start);
- g_free(mixramp_end);
-}
-
-int main(int argc, char **argv)
-{
- const char *uri;
- struct input_stream *is = NULL;
- bool success;
- GError *error = NULL;
- struct playlist_provider *playlist;
- struct song *song;
-
- if (argc != 3) {
- g_printerr("Usage: dump_playlist CONFIG URI\n");
- return 1;
- }
-
- uri = argv[2];
-
- /* initialize GLib */
-
- g_thread_init(NULL);
- g_log_set_default_handler(my_log_func, NULL);
-
- /* initialize MPD */
-
- tag_pool_init();
- config_global_init();
- success = config_read_file(argv[1], &error);
- if (!success) {
- g_printerr("%s\n", error->message);
- g_error_free(error);
- return 1;
- }
-
- io_thread_init();
- if (!io_thread_start(&error)) {
- g_warning("%s", error->message);
- g_error_free(error);
- return EXIT_FAILURE;
- }
-
- if (!input_stream_global_init(&error)) {
- g_warning("%s", error->message);
- g_error_free(error);
- return 2;
- }
-
- playlist_list_global_init();
- decoder_plugin_init_all();
-
- /* open the playlist */
-
- GMutex *mutex = g_mutex_new();
- GCond *cond = g_cond_new();
-
- playlist = playlist_list_open_uri(uri, mutex, cond);
- if (playlist == NULL) {
- /* open the stream and wait until it becomes ready */
-
- is = input_stream_open(uri, mutex, cond, &error);
- if (is == NULL) {
- if (error != NULL) {
- g_warning("%s", error->message);
- g_error_free(error);
- } else
- g_printerr("input_stream_open() failed\n");
- return 2;
- }
-
- input_stream_lock_wait_ready(is);
-
- /* open the playlist */
-
- playlist = playlist_list_open_stream(is, uri);
- if (playlist == NULL) {
- input_stream_close(is);
- g_printerr("Failed to open playlist\n");
- return 2;
- }
- }
-
- /* dump the playlist */
-
- while ((song = playlist_plugin_read(playlist)) != 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(song);
- }
-
- /* deinitialize everything */
-
- playlist_plugin_close(playlist);
- if (is != NULL)
- input_stream_close(is);
-
- g_cond_free(cond);
- g_mutex_free(mutex);
-
- decoder_plugin_deinit_all();
- playlist_list_global_finish();
- input_stream_global_finish();
- io_thread_deinit();
- config_global_finish();
- tag_pool_deinit();
-
- return 0;
-}
diff --git a/test/dump_playlist.cxx b/test/dump_playlist.cxx
new file mode 100644
index 000000000..bf8ddcdfe
--- /dev/null
+++ b/test/dump_playlist.cxx
@@ -0,0 +1,253 @@
+/*
+ * 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.
+ */
+
+#include "config.h"
+#include "TagSave.hxx"
+#include "Song.hxx"
+#include "Directory.hxx"
+#include "input_stream.h"
+#include "conf.h"
+#include "DecoderAPI.hxx"
+#include "DecoderList.hxx"
+#include "InputInit.hxx"
+#include "IOThread.hxx"
+#include "PlaylistRegistry.hxx"
+#include "PlaylistPlugin.hxx"
+#include "fs/Path.hxx"
+
+#include <glib.h>
+
+#include <unistd.h>
+#include <stdlib.h>
+
+Directory::Directory() {}
+Directory::~Directory() {}
+
+static void
+my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level,
+ const gchar *message, G_GNUC_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(G_GNUC_UNUSED struct decoder *decoder,
+ G_GNUC_UNUSED const AudioFormat audio_format,
+ G_GNUC_UNUSED bool seekable,
+ G_GNUC_UNUSED float total_time)
+{
+}
+
+enum decoder_command
+decoder_get_command(G_GNUC_UNUSED struct decoder *decoder)
+{
+ return DECODE_COMMAND_NONE;
+}
+
+void
+decoder_command_finished(G_GNUC_UNUSED struct decoder *decoder)
+{
+}
+
+double
+decoder_seek_where(G_GNUC_UNUSED struct decoder *decoder)
+{
+ return 1.0;
+}
+
+void
+decoder_seek_error(G_GNUC_UNUSED struct decoder *decoder)
+{
+}
+
+size_t
+decoder_read(G_GNUC_UNUSED struct decoder *decoder,
+ struct input_stream *is,
+ void *buffer, size_t length)
+{
+ return input_stream_lock_read(is, buffer, length, NULL);
+}
+
+void
+decoder_timestamp(G_GNUC_UNUSED struct decoder *decoder,
+ G_GNUC_UNUSED double t)
+{
+}
+
+enum decoder_command
+decoder_data(G_GNUC_UNUSED struct decoder *decoder,
+ G_GNUC_UNUSED struct input_stream *is,
+ const void *data, size_t datalen,
+ G_GNUC_UNUSED uint16_t kbit_rate)
+{
+ G_GNUC_UNUSED ssize_t nbytes = write(1, data, datalen);
+ return DECODE_COMMAND_NONE;
+}
+
+enum decoder_command
+decoder_tag(G_GNUC_UNUSED struct decoder *decoder,
+ G_GNUC_UNUSED struct input_stream *is,
+ G_GNUC_UNUSED Tag &&tag)
+{
+ return DECODE_COMMAND_NONE;
+}
+
+void
+decoder_replay_gain(G_GNUC_UNUSED struct decoder *decoder,
+ const struct replay_gain_info *replay_gain_info)
+{
+ const struct replay_gain_tuple *tuple =
+ &replay_gain_info->tuples[REPLAY_GAIN_ALBUM];
+ if (replay_gain_tuple_defined(tuple))
+ g_printerr("replay_gain[album]: gain=%f peak=%f\n",
+ tuple->gain, tuple->peak);
+
+ tuple = &replay_gain_info->tuples[REPLAY_GAIN_TRACK];
+ if (replay_gain_tuple_defined(tuple))
+ g_printerr("replay_gain[track]: gain=%f peak=%f\n",
+ tuple->gain, tuple->peak);
+}
+
+void
+decoder_mixramp(G_GNUC_UNUSED struct decoder *decoder,
+ char *mixramp_start, char *mixramp_end)
+{
+ g_free(mixramp_start);
+ g_free(mixramp_end);
+}
+
+int main(int argc, char **argv)
+{
+ const char *uri;
+ struct input_stream *is = NULL;
+ GError *error = NULL;
+ struct playlist_provider *playlist;
+ Song *song;
+
+ if (argc != 3) {
+ g_printerr("Usage: dump_playlist CONFIG URI\n");
+ return 1;
+ }
+
+ const Path config_path = Path::FromFS(argv[1]);
+ uri = argv[2];
+
+ /* initialize GLib */
+
+#if !GLIB_CHECK_VERSION(2,32,0)
+ g_thread_init(NULL);
+#endif
+
+ g_log_set_default_handler(my_log_func, NULL);
+
+ /* initialize MPD */
+
+ config_global_init();
+ if (!ReadConfigFile(config_path, &error)) {
+ g_printerr("%s\n", error->message);
+ g_error_free(error);
+ return 1;
+ }
+
+ io_thread_init();
+ if (!io_thread_start(&error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
+ if (!input_stream_global_init(&error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return 2;
+ }
+
+ playlist_list_global_init();
+ decoder_plugin_init_all();
+
+ /* open the playlist */
+
+ Mutex mutex;
+ Cond cond;
+
+ playlist = playlist_list_open_uri(uri, mutex, cond);
+ if (playlist == NULL) {
+ /* open the stream and wait until it becomes ready */
+
+ is = input_stream_open(uri, mutex, cond, &error);
+ if (is == NULL) {
+ if (error != NULL) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ } else
+ g_printerr("input_stream_open() failed\n");
+ return 2;
+ }
+
+ input_stream_lock_wait_ready(is);
+
+ /* open the playlist */
+
+ playlist = playlist_list_open_stream(is, uri);
+ if (playlist == NULL) {
+ input_stream_close(is);
+ g_printerr("Failed to open playlist\n");
+ return 2;
+ }
+ }
+
+ /* dump the playlist */
+
+ while ((song = playlist_plugin_read(playlist)) != 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();
+ }
+
+ /* deinitialize everything */
+
+ playlist_plugin_close(playlist);
+ if (is != NULL)
+ input_stream_close(is);
+
+ decoder_plugin_deinit_all();
+ playlist_list_global_finish();
+ input_stream_global_finish();
+ io_thread_deinit();
+ config_global_finish();
+
+ return 0;
+}
diff --git a/test/dump_rva2.c b/test/dump_rva2.c
deleted file mode 100644
index 4a726518a..000000000
--- a/test/dump_rva2.c
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * 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 "tag_id3.h"
-#include "tag_rva2.h"
-#include "replay_gain_info.h"
-#include "conf.h"
-#include "tag.h"
-
-#include <id3tag.h>
-
-#ifdef HAVE_LOCALE_H
-#include <locale.h>
-#endif
-
-#include <stdlib.h>
-
-const char *
-config_get_string(G_GNUC_UNUSED const char *name, const char *default_value)
-{
- return default_value;
-}
-
-struct tag *
-tag_new(void)
-{
- return NULL;
-}
-
-void
-tag_add_item_n(G_GNUC_UNUSED struct tag *tag, G_GNUC_UNUSED enum tag_type type,
- G_GNUC_UNUSED const char *value, G_GNUC_UNUSED size_t len)
-{
-}
-
-void
-tag_free(struct tag *tag)
-{
- g_free(tag);
-}
-
-int main(int argc, char **argv)
-{
- GError *error = NULL;
-
-#ifdef HAVE_LOCALE_H
- /* initialize locale */
- setlocale(LC_CTYPE,"");
-#endif
-
- if (argc != 2) {
- g_printerr("Usage: read_rva2 FILE\n");
- return 1;
- }
-
- const char *path = argv[1];
-
- struct id3_tag *tag = tag_id3_load(path, &error);
- if (tag == NULL) {
- if (error != NULL) {
- g_printerr("%s\n", error->message);
- g_error_free(error);
- } else
- g_printerr("No ID3 tag found\n");
-
- return EXIT_FAILURE;
- }
-
- struct replay_gain_info replay_gain;
- replay_gain_info_init(&replay_gain);
-
- bool success = tag_rva2_parse(tag, &replay_gain);
- id3_tag_delete(tag);
-
- if (!success) {
- g_printerr("No RVA2 tag found\n");
- return EXIT_FAILURE;
- }
-
- const struct replay_gain_tuple *tuple =
- &replay_gain.tuples[REPLAY_GAIN_ALBUM];
- if (replay_gain_tuple_defined(tuple))
- g_printerr("replay_gain[album]: gain=%f peak=%f\n",
- tuple->gain, tuple->peak);
-
- tuple = &replay_gain.tuples[REPLAY_GAIN_TRACK];
- if (replay_gain_tuple_defined(tuple))
- g_printerr("replay_gain[track]: gain=%f peak=%f\n",
- tuple->gain, tuple->peak);
-
- return EXIT_SUCCESS;
-}
diff --git a/test/dump_rva2.cxx b/test/dump_rva2.cxx
new file mode 100644
index 000000000..9dbb018d6
--- /dev/null
+++ b/test/dump_rva2.cxx
@@ -0,0 +1,102 @@
+/*
+ * 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 "TagId3.hxx"
+#include "TagRva2.hxx"
+#include "replay_gain_info.h"
+#include "conf.h"
+#include "Tag.hxx"
+
+#include <id3tag.h>
+
+#include <glib.h>
+
+#ifdef HAVE_LOCALE_H
+#include <locale.h>
+#endif
+
+#include <stdlib.h>
+
+const char *
+config_get_string(gcc_unused enum ConfigOption option,
+ const char *default_value)
+{
+ return default_value;
+}
+
+void
+Tag::AddItem(gcc_unused enum tag_type type,
+ gcc_unused const char *value)
+{
+}
+
+Tag::~Tag() {}
+
+int main(int argc, char **argv)
+{
+ GError *error = NULL;
+
+#ifdef HAVE_LOCALE_H
+ /* initialize locale */
+ setlocale(LC_CTYPE,"");
+#endif
+
+ if (argc != 2) {
+ g_printerr("Usage: read_rva2 FILE\n");
+ return 1;
+ }
+
+ const char *path = argv[1];
+
+ struct id3_tag *tag = tag_id3_load(path, &error);
+ if (tag == NULL) {
+ if (error != NULL) {
+ g_printerr("%s\n", error->message);
+ g_error_free(error);
+ } else
+ g_printerr("No ID3 tag found\n");
+
+ return EXIT_FAILURE;
+ }
+
+ struct replay_gain_info replay_gain;
+ replay_gain_info_init(&replay_gain);
+
+ bool success = tag_rva2_parse(tag, &replay_gain);
+ id3_tag_delete(tag);
+
+ if (!success) {
+ g_printerr("No RVA2 tag found\n");
+ return EXIT_FAILURE;
+ }
+
+ const struct replay_gain_tuple *tuple =
+ &replay_gain.tuples[REPLAY_GAIN_ALBUM];
+ if (replay_gain_tuple_defined(tuple))
+ g_printerr("replay_gain[album]: gain=%f peak=%f\n",
+ tuple->gain, tuple->peak);
+
+ tuple = &replay_gain.tuples[REPLAY_GAIN_TRACK];
+ if (replay_gain_tuple_defined(tuple))
+ g_printerr("replay_gain[track]: gain=%f peak=%f\n",
+ tuple->gain, tuple->peak);
+
+ return EXIT_SUCCESS;
+}
diff --git a/test/dump_text_file.c b/test/dump_text_file.c
deleted file mode 100644
index f14371441..000000000
--- a/test/dump_text_file.c
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "io_thread.h"
-#include "input_init.h"
-#include "input_stream.h"
-#include "text_input_stream.h"
-#include "tag_pool.h"
-#include "conf.h"
-#include "stdbin.h"
-
-#ifdef ENABLE_ARCHIVE
-#include "archive_list.h"
-#endif
-
-#include <glib.h>
-
-#include <unistd.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-static void
-my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level,
- const gchar *message, G_GNUC_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(struct text_input_stream *is)
-{
- const char *line;
- while ((line = text_input_stream_read(is)) != NULL)
- printf("'%s'\n", line);
-}
-
-static int
-dump_input_stream(struct input_stream *is)
-{
- GError *error = NULL;
-
- input_stream_lock(is);
-
- /* wait until the stream becomes ready */
-
- input_stream_wait_ready(is);
-
- if (!input_stream_check(is, &error)) {
- g_warning("%s", error->message);
- g_error_free(error);
- input_stream_unlock(is);
- return EXIT_FAILURE;
- }
-
- /* read data and tags from the stream */
-
- input_stream_unlock(is);
-
- struct text_input_stream *tis = text_input_stream_new(is);
- dump_text_file(tis);
- text_input_stream_free(tis);
-
- input_stream_lock(is);
-
- if (!input_stream_check(is, &error)) {
- g_warning("%s", error->message);
- g_error_free(error);
- input_stream_unlock(is);
- return EXIT_FAILURE;
- }
-
- input_stream_unlock(is);
-
- return 0;
-}
-
-int main(int argc, char **argv)
-{
- GError *error = NULL;
- struct input_stream *is;
- int ret;
-
- if (argc != 2) {
- g_printerr("Usage: run_input URI\n");
- return 1;
- }
-
- /* initialize GLib */
-
- g_thread_init(NULL);
- g_log_set_default_handler(my_log_func, NULL);
-
- /* initialize MPD */
-
- tag_pool_init();
- config_global_init();
-
- io_thread_init();
- if (!io_thread_start(&error)) {
- g_warning("%s", error->message);
- g_error_free(error);
- return EXIT_FAILURE;
- }
-
-#ifdef ENABLE_ARCHIVE
- archive_plugin_init_all();
-#endif
-
- if (!input_stream_global_init(&error)) {
- g_warning("%s", error->message);
- g_error_free(error);
- return 2;
- }
-
- /* open the stream and dump it */
-
- GMutex *mutex = g_mutex_new();
- GCond *cond = g_cond_new();
-
- is = input_stream_open(argv[1], mutex, cond, &error);
- if (is != NULL) {
- ret = dump_input_stream(is);
- input_stream_close(is);
- } else {
- if (error != NULL) {
- g_warning("%s", error->message);
- g_error_free(error);
- } else
- g_printerr("input_stream_open() failed\n");
- ret = 2;
- }
-
- g_cond_free(cond);
- g_mutex_free(mutex);
-
- /* deinitialize everything */
-
- input_stream_global_finish();
-
-#ifdef ENABLE_ARCHIVE
- archive_plugin_deinit_all();
-#endif
-
- io_thread_deinit();
-
- config_global_finish();
- tag_pool_deinit();
-
- return ret;
-}
diff --git a/test/dump_text_file.cxx b/test/dump_text_file.cxx
new file mode 100644
index 000000000..40b3a878f
--- /dev/null
+++ b/test/dump_text_file.cxx
@@ -0,0 +1,166 @@
+/*
+ * 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.
+ */
+
+#include "config.h"
+#include "IOThread.hxx"
+#include "InputInit.hxx"
+#include "InputStream.hxx"
+#include "conf.h"
+#include "stdbin.h"
+#include "TextInputStream.hxx"
+
+#ifdef ENABLE_ARCHIVE
+#include "ArchiveList.hxx"
+#endif
+
+#include <glib.h>
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+static void
+my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level,
+ const gchar *message, G_GNUC_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;
+ while (is.ReadLine(line))
+ printf("'%s'\n", line.c_str());
+}
+
+static int
+dump_input_stream(struct input_stream *is)
+{
+ GError *error = NULL;
+
+ input_stream_lock(is);
+
+ /* wait until the stream becomes ready */
+
+ input_stream_wait_ready(is);
+
+ if (!input_stream_check(is, &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ input_stream_unlock(is);
+ return EXIT_FAILURE;
+ }
+
+ /* read data and tags from the stream */
+
+ input_stream_unlock(is);
+ {
+ TextInputStream tis(is);
+ dump_text_file(tis);
+ }
+ input_stream_lock(is);
+
+ if (!input_stream_check(is, &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ input_stream_unlock(is);
+ return EXIT_FAILURE;
+ }
+
+ input_stream_unlock(is);
+
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ GError *error = NULL;
+ struct input_stream *is;
+ int ret;
+
+ if (argc != 2) {
+ g_printerr("Usage: run_input URI\n");
+ return 1;
+ }
+
+ /* initialize GLib */
+
+#if !GLIB_CHECK_VERSION(2,32,0)
+ g_thread_init(NULL);
+#endif
+
+ g_log_set_default_handler(my_log_func, NULL);
+
+ /* initialize MPD */
+
+ config_global_init();
+
+ io_thread_init();
+ if (!io_thread_start(&error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
+#ifdef ENABLE_ARCHIVE
+ archive_plugin_init_all();
+#endif
+
+ if (!input_stream_global_init(&error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return 2;
+ }
+
+ /* open the stream and dump it */
+
+ Mutex mutex;
+ Cond cond;
+
+ is = input_stream_open(argv[1], mutex, cond, &error);
+ if (is != NULL) {
+ ret = dump_input_stream(is);
+ input_stream_close(is);
+ } else {
+ if (error != NULL) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ } else
+ g_printerr("input_stream_open() failed\n");
+ ret = 2;
+ }
+
+ /* deinitialize everything */
+
+ input_stream_global_finish();
+
+#ifdef ENABLE_ARCHIVE
+ archive_plugin_deinit_all();
+#endif
+
+ io_thread_deinit();
+
+ config_global_finish();
+
+ return ret;
+}
diff --git a/test/read_conf.c b/test/read_conf.c
deleted file mode 100644
index 4f6005c6f..000000000
--- a/test/read_conf.c
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "conf.h"
-
-#include <glib.h>
-
-#include <assert.h>
-
-static void
-my_log_func(G_GNUC_UNUSED const gchar *log_domain,
- GLogLevelFlags log_level,
- const gchar *message, G_GNUC_UNUSED gpointer user_data)
-{
- if (log_level > G_LOG_LEVEL_WARNING)
- return;
-
- g_printerr("%s\n", message);
-}
-
-int main(int argc, char **argv)
-{
- if (argc != 3) {
- g_printerr("Usage: read_conf FILE SETTING\n");
- return 1;
- }
-
- const char *path = argv[1];
- const char *name = argv[2];
-
- g_log_set_default_handler(my_log_func, NULL);
-
- config_global_init();
-
- GError *error = NULL;
- bool success = config_read_file(path, &error);
- if (!success) {
- g_printerr("%s:", error->message);
- g_error_free(error);
- return 1;
- }
-
- const char *value = config_get_string(name, NULL);
- int ret;
- if (value != NULL) {
- g_print("%s\n", value);
- ret = 0;
- } else {
- g_printerr("No such setting: %s\n", name);
- ret = 2;
- }
-
- config_global_finish();
- return ret;
-}
diff --git a/test/read_conf.cxx b/test/read_conf.cxx
new file mode 100644
index 000000000..8759398da
--- /dev/null
+++ b/test/read_conf.cxx
@@ -0,0 +1,75 @@
+/*
+ * 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 "conf.h"
+#include "fs/Path.hxx"
+
+#include <glib.h>
+
+#include <assert.h>
+
+static void
+my_log_func(G_GNUC_UNUSED const gchar *log_domain,
+ GLogLevelFlags log_level,
+ const gchar *message, G_GNUC_UNUSED gpointer user_data)
+{
+ if (log_level > G_LOG_LEVEL_WARNING)
+ return;
+
+ g_printerr("%s\n", message);
+}
+
+int main(int argc, char **argv)
+{
+ if (argc != 3) {
+ g_printerr("Usage: read_conf FILE SETTING\n");
+ return 1;
+ }
+
+ 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();
+
+ GError *error = NULL;
+ if (!ReadConfigFile(config_path, &error)) {
+ g_printerr("%s:", error->message);
+ g_error_free(error);
+ return 1;
+ }
+
+ ConfigOption option = ParseConfigOptionName(name);
+ const char *value = option != CONF_MAX
+ ? config_get_string(option, nullptr)
+ : nullptr;
+ int ret;
+ if (value != NULL) {
+ g_print("%s\n", value);
+ ret = 0;
+ } else {
+ g_printerr("No such setting: %s\n", name);
+ ret = 2;
+ }
+
+ config_global_finish();
+ return ret;
+}
diff --git a/test/read_mixer.c b/test/read_mixer.c
deleted file mode 100644
index f6de8177d..000000000
--- a/test/read_mixer.c
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "mixer_control.h"
-#include "mixer_list.h"
-#include "filter_registry.h"
-#include "pcm_volume.h"
-#include "event_pipe.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-#include <unistd.h>
-
-#ifdef HAVE_PULSE
-#include "output/pulse_output_plugin.h"
-
-void
-pulse_output_lock(G_GNUC_UNUSED struct pulse_output *po)
-{
-}
-
-void
-pulse_output_unlock(G_GNUC_UNUSED struct pulse_output *po)
-{
-}
-
-void
-pulse_output_set_mixer(G_GNUC_UNUSED struct pulse_output *po,
- G_GNUC_UNUSED struct pulse_mixer *pm)
-{
-}
-
-void
-pulse_output_clear_mixer(G_GNUC_UNUSED struct pulse_output *po,
- G_GNUC_UNUSED struct pulse_mixer *pm)
-{
-}
-
-bool
-pulse_output_set_volume(G_GNUC_UNUSED struct pulse_output *po,
- G_GNUC_UNUSED const struct pa_cvolume *volume,
- G_GNUC_UNUSED GError **error_r)
-{
- return false;
-}
-
-#endif
-
-#ifdef HAVE_ROAR
-#include "output/roar_output_plugin.h"
-
-int
-roar_output_get_volume(G_GNUC_UNUSED struct roar *roar)
-{
- return -1;
-}
-
-bool
-roar_output_set_volume(G_GNUC_UNUSED struct roar *roar,
- G_GNUC_UNUSED unsigned volume)
-{
- return true;
-}
-
-#endif
-
-void
-event_pipe_emit(G_GNUC_UNUSED enum pipe_event event)
-{
-}
-
-const struct filter_plugin *
-filter_plugin_by_name(G_GNUC_UNUSED const char *name)
-{
- assert(false);
- return NULL;
-}
-
-bool
-pcm_volume(G_GNUC_UNUSED void *buffer, G_GNUC_UNUSED size_t length,
- G_GNUC_UNUSED enum sample_format format,
- G_GNUC_UNUSED int volume)
-{
- assert(false);
- return false;
-}
-
-int main(int argc, G_GNUC_UNUSED char **argv)
-{
- GError *error = NULL;
- struct mixer *mixer;
- bool success;
- int volume;
-
- if (argc != 2) {
- g_printerr("Usage: read_mixer PLUGIN\n");
- return 1;
- }
-
- g_thread_init(NULL);
-
- mixer = mixer_new(&alsa_mixer_plugin, NULL, NULL, &error);
- if (mixer == NULL) {
- g_printerr("mixer_new() failed: %s\n", error->message);
- g_error_free(error);
- return 2;
- }
-
- success = mixer_open(mixer, &error);
- if (!success) {
- mixer_free(mixer);
- g_printerr("failed to open the mixer: %s\n", error->message);
- g_error_free(error);
- return 2;
- }
-
- volume = mixer_get_volume(mixer, &error);
- mixer_close(mixer);
- mixer_free(mixer);
-
- assert(volume >= -1 && volume <= 100);
-
- if (volume < 0) {
- if (error != NULL) {
- g_printerr("failed to read volume: %s\n",
- error->message);
- g_error_free(error);
- } else
- g_printerr("failed to read volume\n");
- return 2;
- }
-
- g_print("%d\n", volume);
- return 0;
-}
diff --git a/test/read_mixer.cxx b/test/read_mixer.cxx
new file mode 100644
index 000000000..54c92eea3
--- /dev/null
+++ b/test/read_mixer.cxx
@@ -0,0 +1,165 @@
+/*
+ * 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.
+ */
+
+#include "config.h"
+#include "MixerControl.hxx"
+#include "MixerList.hxx"
+#include "FilterRegistry.hxx"
+#include "pcm/PcmVolume.hxx"
+#include "GlobalEvents.hxx"
+#include "Main.hxx"
+#include "event/Loop.hxx"
+#include "ConfigData.hxx"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+#include <unistd.h>
+
+EventLoop *main_loop;
+
+#ifdef HAVE_PULSE
+#include "output/PulseOutputPlugin.hxx"
+
+void
+pulse_output_lock(G_GNUC_UNUSED PulseOutput *po)
+{
+}
+
+void
+pulse_output_unlock(G_GNUC_UNUSED PulseOutput *po)
+{
+}
+
+void
+pulse_output_set_mixer(G_GNUC_UNUSED PulseOutput *po,
+ G_GNUC_UNUSED PulseMixer *pm)
+{
+}
+
+void
+pulse_output_clear_mixer(G_GNUC_UNUSED PulseOutput *po,
+ G_GNUC_UNUSED PulseMixer *pm)
+{
+}
+
+bool
+pulse_output_set_volume(G_GNUC_UNUSED PulseOutput *po,
+ G_GNUC_UNUSED const struct pa_cvolume *volume,
+ G_GNUC_UNUSED GError **error_r)
+{
+ return false;
+}
+
+#endif
+
+#ifdef HAVE_ROAR
+#include "output/RoarOutputPlugin.hxx"
+
+int
+roar_output_get_volume(gcc_unused RoarOutput *roar)
+{
+ return -1;
+}
+
+bool
+roar_output_set_volume(gcc_unused RoarOutput *roar,
+ G_GNUC_UNUSED unsigned volume)
+{
+ return true;
+}
+
+#endif
+
+void
+GlobalEvents::Emit(gcc_unused Event event)
+{
+}
+
+const struct filter_plugin *
+filter_plugin_by_name(G_GNUC_UNUSED const char *name)
+{
+ assert(false);
+ return NULL;
+}
+
+bool
+pcm_volume(G_GNUC_UNUSED void *buffer, G_GNUC_UNUSED size_t length,
+ G_GNUC_UNUSED SampleFormat format,
+ G_GNUC_UNUSED int volume)
+{
+ assert(false);
+ return false;
+}
+
+int main(int argc, G_GNUC_UNUSED char **argv)
+{
+ GError *error = NULL;
+ bool success;
+ int volume;
+
+ if (argc != 2) {
+ g_printerr("Usage: read_mixer PLUGIN\n");
+ return 1;
+ }
+
+#if !GLIB_CHECK_VERSION(2,32,0)
+ g_thread_init(NULL);
+#endif
+
+ main_loop = new EventLoop(EventLoop::Default());
+
+ Mixer *mixer = mixer_new(&alsa_mixer_plugin, nullptr,
+ config_param(), &error);
+ if (mixer == NULL) {
+ g_printerr("mixer_new() failed: %s\n", error->message);
+ g_error_free(error);
+ return 2;
+ }
+
+ success = mixer_open(mixer, &error);
+ if (!success) {
+ mixer_free(mixer);
+ g_printerr("failed to open the mixer: %s\n", error->message);
+ g_error_free(error);
+ return 2;
+ }
+
+ volume = mixer_get_volume(mixer, &error);
+ mixer_close(mixer);
+ mixer_free(mixer);
+
+ delete main_loop;
+
+ assert(volume >= -1 && volume <= 100);
+
+ if (volume < 0) {
+ if (error != NULL) {
+ g_printerr("failed to read volume: %s\n",
+ error->message);
+ g_error_free(error);
+ } else
+ g_printerr("failed to read volume\n");
+ return 2;
+ }
+
+ g_print("%d\n", volume);
+ return 0;
+}
diff --git a/test/read_tags.c b/test/read_tags.c
deleted file mode 100644
index faf9a45c0..000000000
--- a/test/read_tags.c
+++ /dev/null
@@ -1,245 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "io_thread.h"
-#include "decoder_list.h"
-#include "decoder_api.h"
-#include "input_init.h"
-#include "input_stream.h"
-#include "audio_format.h"
-#include "pcm_volume.h"
-#include "tag_ape.h"
-#include "tag_id3.h"
-#include "tag_handler.h"
-#include "idle.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <unistd.h>
-#include <stdlib.h>
-
-#ifdef HAVE_LOCALE_H
-#include <locale.h>
-#endif
-
-/**
- * No-op dummy.
- */
-void
-idle_add(G_GNUC_UNUSED unsigned flags)
-{
-}
-
-/**
- * No-op dummy.
- */
-bool
-pcm_volume(G_GNUC_UNUSED void *buffer, G_GNUC_UNUSED size_t length,
- G_GNUC_UNUSED enum sample_format format,
- G_GNUC_UNUSED int volume)
-{
- return true;
-}
-
-void
-decoder_initialized(G_GNUC_UNUSED struct decoder *decoder,
- G_GNUC_UNUSED const struct audio_format *audio_format,
- G_GNUC_UNUSED bool seekable,
- G_GNUC_UNUSED float total_time)
-{
-}
-
-enum decoder_command
-decoder_get_command(G_GNUC_UNUSED struct decoder *decoder)
-{
- return DECODE_COMMAND_NONE;
-}
-
-void decoder_command_finished(G_GNUC_UNUSED struct decoder *decoder)
-{
-}
-
-double decoder_seek_where(G_GNUC_UNUSED struct decoder *decoder)
-{
- return 1.0;
-}
-
-void decoder_seek_error(G_GNUC_UNUSED struct decoder *decoder)
-{
-}
-
-size_t
-decoder_read(G_GNUC_UNUSED struct decoder *decoder,
- struct input_stream *is,
- void *buffer, size_t length)
-{
- return input_stream_lock_read(is, buffer, length, NULL);
-}
-
-void
-decoder_timestamp(G_GNUC_UNUSED struct decoder *decoder,
- G_GNUC_UNUSED double t)
-{
-}
-
-enum decoder_command
-decoder_data(G_GNUC_UNUSED struct decoder *decoder,
- G_GNUC_UNUSED struct input_stream *is,
- const void *data, size_t datalen,
- G_GNUC_UNUSED uint16_t bit_rate)
-{
- G_GNUC_UNUSED ssize_t nbytes = write(1, data, datalen);
- return DECODE_COMMAND_NONE;
-}
-
-enum decoder_command
-decoder_tag(G_GNUC_UNUSED struct decoder *decoder,
- G_GNUC_UNUSED struct input_stream *is,
- G_GNUC_UNUSED const struct tag *tag)
-{
- return DECODE_COMMAND_NONE;
-}
-
-float
-decoder_replay_gain(G_GNUC_UNUSED struct decoder *decoder,
- G_GNUC_UNUSED const struct replay_gain_info *replay_gain_info)
-{
- return 0.0;
-}
-
-void
-decoder_mixramp(G_GNUC_UNUSED struct decoder *decoder,
- G_GNUC_UNUSED float replay_gain_db,
- char *mixramp_start, char *mixramp_end)
-{
- g_free(mixramp_start);
- g_free(mixramp_end);
-}
-
-static bool empty = true;
-
-static void
-print_duration(unsigned seconds, G_GNUC_UNUSED void *ctx)
-{
- g_print("duration=%d\n", seconds);
-}
-
-static void
-print_tag(enum tag_type type, const char *value, G_GNUC_UNUSED void *ctx)
-{
- g_print("[%s]=%s\n", tag_item_names[type], value);
- empty = false;
-}
-
-static void
-print_pair(const char *name, const char *value, G_GNUC_UNUSED void *ctx)
-{
- g_print("\"%s\"=%s\n", name, value);
-}
-
-static const struct tag_handler print_handler = {
- .duration = print_duration,
- .tag = print_tag,
- .pair = print_pair,
-};
-
-int main(int argc, char **argv)
-{
- GError *error = NULL;
- const char *decoder_name, *path;
- const struct decoder_plugin *plugin;
-
-#ifdef HAVE_LOCALE_H
- /* initialize locale */
- setlocale(LC_CTYPE,"");
-#endif
-
- if (argc != 3) {
- g_printerr("Usage: read_tags DECODER FILE\n");
- return 1;
- }
-
- decoder_name = argv[1];
- path = argv[2];
-
- g_thread_init(NULL);
- io_thread_init();
- if (!io_thread_start(&error)) {
- g_warning("%s", error->message);
- g_error_free(error);
- return EXIT_FAILURE;
- }
-
- if (!input_stream_global_init(&error)) {
- g_warning("%s", error->message);
- g_error_free(error);
- return 2;
- }
-
- decoder_plugin_init_all();
-
- plugin = decoder_plugin_from_name(decoder_name);
- if (plugin == NULL) {
- g_printerr("No such decoder: %s\n", decoder_name);
- return 1;
- }
-
- bool success = decoder_plugin_scan_file(plugin, path,
- &print_handler, NULL);
- if (!success && plugin->scan_stream != NULL) {
- GMutex *mutex = g_mutex_new();
- GCond *cond = g_cond_new();
-
- struct input_stream *is =
- input_stream_open(path, mutex, cond, &error);
-
- if (is == NULL) {
- g_printerr("Failed to open %s: %s\n",
- path, error->message);
- g_error_free(error);
- return 1;
- }
-
- success = decoder_plugin_scan_stream(plugin, is,
- &print_handler, NULL);
- input_stream_close(is);
-
- g_cond_free(cond);
- g_mutex_free(mutex);
- }
-
- decoder_plugin_deinit_all();
- input_stream_global_finish();
- io_thread_deinit();
-
- if (!success) {
- g_printerr("Failed to read tags\n");
- return 1;
- }
-
- if (empty) {
- tag_ape_scan2(path, &print_handler, NULL);
- if (empty)
- tag_id3_scan(path, &print_handler, NULL);
- }
-
- return 0;
-}
diff --git a/test/read_tags.cxx b/test/read_tags.cxx
new file mode 100644
index 000000000..420cf9f0e
--- /dev/null
+++ b/test/read_tags.cxx
@@ -0,0 +1,241 @@
+/*
+ * 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.
+ */
+
+#include "config.h"
+#include "IOThread.hxx"
+#include "DecoderList.hxx"
+#include "DecoderAPI.hxx"
+#include "InputInit.hxx"
+#include "InputStream.hxx"
+#include "AudioFormat.hxx"
+#include "TagHandler.hxx"
+#include "TagId3.hxx"
+#include "ApeTag.hxx"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#ifdef HAVE_LOCALE_H
+#include <locale.h>
+#endif
+
+void
+decoder_initialized(G_GNUC_UNUSED struct decoder *decoder,
+ G_GNUC_UNUSED const AudioFormat audio_format,
+ G_GNUC_UNUSED bool seekable,
+ G_GNUC_UNUSED float total_time)
+{
+}
+
+enum decoder_command
+decoder_get_command(G_GNUC_UNUSED struct decoder *decoder)
+{
+ return DECODE_COMMAND_NONE;
+}
+
+void decoder_command_finished(G_GNUC_UNUSED struct decoder *decoder)
+{
+}
+
+double decoder_seek_where(G_GNUC_UNUSED struct decoder *decoder)
+{
+ return 1.0;
+}
+
+void decoder_seek_error(G_GNUC_UNUSED struct decoder *decoder)
+{
+}
+
+size_t
+decoder_read(G_GNUC_UNUSED struct decoder *decoder,
+ struct input_stream *is,
+ void *buffer, size_t length)
+{
+ return input_stream_lock_read(is, buffer, length, NULL);
+}
+
+void
+decoder_timestamp(G_GNUC_UNUSED struct decoder *decoder,
+ G_GNUC_UNUSED double t)
+{
+}
+
+enum decoder_command
+decoder_data(G_GNUC_UNUSED struct decoder *decoder,
+ G_GNUC_UNUSED struct input_stream *is,
+ const void *data, size_t datalen,
+ G_GNUC_UNUSED uint16_t bit_rate)
+{
+ G_GNUC_UNUSED ssize_t nbytes = write(1, data, datalen);
+ return DECODE_COMMAND_NONE;
+}
+
+enum decoder_command
+decoder_tag(G_GNUC_UNUSED struct decoder *decoder,
+ G_GNUC_UNUSED struct input_stream *is,
+ G_GNUC_UNUSED Tag &&tag)
+{
+ return DECODE_COMMAND_NONE;
+}
+
+void
+decoder_replay_gain(G_GNUC_UNUSED struct decoder *decoder,
+ G_GNUC_UNUSED const struct replay_gain_info *replay_gain_info)
+{
+}
+
+void
+decoder_mixramp(G_GNUC_UNUSED struct decoder *decoder,
+ char *mixramp_start, char *mixramp_end)
+{
+ g_free(mixramp_start);
+ g_free(mixramp_end);
+}
+
+static bool empty = true;
+
+static void
+print_duration(unsigned seconds, G_GNUC_UNUSED void *ctx)
+{
+ g_print("duration=%d\n", seconds);
+}
+
+static void
+print_tag(enum tag_type type, const char *value, G_GNUC_UNUSED void *ctx)
+{
+ g_print("[%s]=%s\n", tag_item_names[type], value);
+ empty = false;
+}
+
+static void
+print_pair(const char *name, const char *value, G_GNUC_UNUSED void *ctx)
+{
+ g_print("\"%s\"=%s\n", name, value);
+}
+
+static const struct tag_handler print_handler = {
+ print_duration,
+ print_tag,
+ print_pair,
+};
+
+int main(int argc, char **argv)
+{
+ GError *error = NULL;
+ const char *decoder_name, *path;
+ const struct decoder_plugin *plugin;
+
+#ifdef HAVE_LOCALE_H
+ /* initialize locale */
+ setlocale(LC_CTYPE,"");
+#endif
+
+ if (argc != 3) {
+ g_printerr("Usage: read_tags DECODER FILE\n");
+ return 1;
+ }
+
+ decoder_name = argv[1];
+ path = argv[2];
+
+#if !GLIB_CHECK_VERSION(2,32,0)
+ g_thread_init(NULL);
+#endif
+
+ io_thread_init();
+ if (!io_thread_start(&error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
+ if (!input_stream_global_init(&error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return 2;
+ }
+
+ decoder_plugin_init_all();
+
+ plugin = decoder_plugin_from_name(decoder_name);
+ if (plugin == NULL) {
+ g_printerr("No such decoder: %s\n", decoder_name);
+ return 1;
+ }
+
+ bool success = decoder_plugin_scan_file(plugin, path,
+ &print_handler, NULL);
+ if (!success && plugin->scan_stream != NULL) {
+ Mutex mutex;
+ Cond cond;
+
+ struct input_stream *is =
+ input_stream_open(path, mutex, cond, &error);
+
+ if (is == NULL) {
+ g_printerr("Failed to open %s: %s\n",
+ path, error->message);
+ g_error_free(error);
+ return 1;
+ }
+
+ mutex.lock();
+
+ while (!is->ready) {
+ cond.wait(mutex);
+ input_stream_update(is);
+ }
+
+ if (!input_stream_check(is, &error)) {
+ mutex.unlock();
+
+ g_printerr("Failed to read %s: %s\n",
+ path, error->message);
+ g_error_free(error);
+
+ return EXIT_FAILURE;
+ }
+
+ mutex.unlock();
+
+ success = decoder_plugin_scan_stream(plugin, is,
+ &print_handler, NULL);
+ input_stream_close(is);
+ }
+
+ decoder_plugin_deinit_all();
+ input_stream_global_finish();
+ io_thread_deinit();
+
+ if (!success) {
+ g_printerr("Failed to read tags\n");
+ return 1;
+ }
+
+ if (empty) {
+ tag_ape_scan2(path, &print_handler, NULL);
+ if (empty)
+ tag_id3_scan(path, &print_handler, NULL);
+ }
+
+ return 0;
+}
diff --git a/test/run_convert.c b/test/run_convert.c
deleted file mode 100644
index 4f57400fd..000000000
--- a/test/run_convert.c
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * This program is a command line interface to MPD's PCM conversion
- * library (pcm_convert.c).
- *
- */
-
-#include "config.h"
-#include "audio_parser.h"
-#include "audio_format.h"
-#include "pcm_convert.h"
-#include "conf.h"
-#include "fifo_buffer.h"
-#include "stdbin.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <stddef.h>
-#include <unistd.h>
-
-static void
-my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level,
- const gchar *message, G_GNUC_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(G_GNUC_UNUSED const char *name, const char *default_value)
-{
- return default_value;
-}
-
-int main(int argc, char **argv)
-{
- GError *error = NULL;
- struct audio_format in_audio_format, out_audio_format;
- struct pcm_convert_state state;
- const void *output;
- ssize_t nbytes;
- size_t length;
-
- if (argc != 3) {
- g_printerr("Usage: run_convert IN_FORMAT OUT_FORMAT <IN >OUT\n");
- return 1;
- }
-
- g_log_set_default_handler(my_log_func, NULL);
-
- if (!audio_format_parse(&in_audio_format, argv[1],
- false, &error)) {
- g_printerr("Failed to parse audio format: %s\n",
- error->message);
- return 1;
- }
-
- struct audio_format 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->message);
- return 1;
- }
-
- out_audio_format = in_audio_format;
- audio_format_mask_apply(&out_audio_format, &out_audio_format_mask);
-
- const size_t in_frame_size = audio_format_frame_size(&in_audio_format);
-
- pcm_convert_init(&state);
-
- struct fifo_buffer *buffer = fifo_buffer_new(4096);
-
- while (true) {
- void *p = fifo_buffer_write(buffer, &length);
- assert(p != NULL);
-
- nbytes = read(0, p, length);
- if (nbytes <= 0)
- break;
-
- fifo_buffer_append(buffer, nbytes);
-
- const void *src = fifo_buffer_read(buffer, &length);
- assert(src != NULL);
-
- length -= length % in_frame_size;
- if (length == 0)
- continue;
-
- fifo_buffer_consume(buffer, length);
-
- output = pcm_convert(&state, &in_audio_format, src, length,
- &out_audio_format, &length, &error);
- if (output == NULL) {
- g_printerr("Failed to convert: %s\n", error->message);
- return 2;
- }
-
- G_GNUC_UNUSED ssize_t ignored = write(1, output, length);
- }
-
- pcm_convert_deinit(&state);
-}
diff --git a/test/run_convert.cxx b/test/run_convert.cxx
new file mode 100644
index 000000000..ce7df42c3
--- /dev/null
+++ b/test/run_convert.cxx
@@ -0,0 +1,126 @@
+/*
+ * 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.
+ */
+
+/*
+ * This program is a command line interface to MPD's PCM conversion
+ * library (pcm_convert.c).
+ *
+ */
+
+#include "config.h"
+#include "AudioParser.hxx"
+#include "AudioFormat.hxx"
+#include "pcm/PcmConvert.hxx"
+#include "conf.h"
+#include "util/fifo_buffer.h"
+#include "stdbin.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <stddef.h>
+#include <unistd.h>
+
+static void
+my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level,
+ const gchar *message, G_GNUC_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)
+{
+ return default_value;
+}
+
+int main(int argc, char **argv)
+{
+ GError *error = NULL;
+ AudioFormat in_audio_format, out_audio_format;
+ const void *output;
+ ssize_t nbytes;
+ size_t length;
+
+ if (argc != 3) {
+ g_printerr("Usage: run_convert IN_FORMAT OUT_FORMAT <IN >OUT\n");
+ return 1;
+ }
+
+ g_log_set_default_handler(my_log_func, NULL);
+
+ if (!audio_format_parse(in_audio_format, argv[1],
+ false, &error)) {
+ g_printerr("Failed to parse audio format: %s\n",
+ error->message);
+ return 1;
+ }
+
+ 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->message);
+ return 1;
+ }
+
+ out_audio_format = in_audio_format;
+ out_audio_format.ApplyMask(out_audio_format_mask);
+
+ const size_t in_frame_size = in_audio_format.GetFrameSize();
+
+ PcmConvert state;
+
+ struct fifo_buffer *buffer = fifo_buffer_new(4096);
+
+ while (true) {
+ void *p = fifo_buffer_write(buffer, &length);
+ assert(p != NULL);
+
+ nbytes = read(0, p, length);
+ if (nbytes <= 0)
+ break;
+
+ fifo_buffer_append(buffer, nbytes);
+
+ const void *src = fifo_buffer_read(buffer, &length);
+ assert(src != NULL);
+
+ length -= length % in_frame_size;
+ if (length == 0)
+ continue;
+
+ fifo_buffer_consume(buffer, length);
+
+ output = state.Convert(in_audio_format, src, length,
+ out_audio_format, &length, &error);
+ if (output == NULL) {
+ g_printerr("Failed to convert: %s\n", error->message);
+ return 2;
+ }
+
+ G_GNUC_UNUSED ssize_t ignored = write(1, output, length);
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/test/run_decoder.c b/test/run_decoder.c
deleted file mode 100644
index e6712c75b..000000000
--- a/test/run_decoder.c
+++ /dev/null
@@ -1,254 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "io_thread.h"
-#include "decoder_list.h"
-#include "decoder_api.h"
-#include "tag_pool.h"
-#include "input_init.h"
-#include "input_stream.h"
-#include "audio_format.h"
-#include "pcm_volume.h"
-#include "idle.h"
-#include "stdbin.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <unistd.h>
-#include <stdlib.h>
-
-static void
-my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level,
- const gchar *message, G_GNUC_UNUSED gpointer user_data)
-{
- if (log_domain != NULL)
- g_printerr("%s: %s\n", log_domain, message);
- else
- g_printerr("%s\n", message);
-}
-
-/**
- * No-op dummy.
- */
-void
-idle_add(G_GNUC_UNUSED unsigned flags)
-{
-}
-
-/**
- * No-op dummy.
- */
-bool
-pcm_volume(G_GNUC_UNUSED void *buffer, G_GNUC_UNUSED size_t length,
- G_GNUC_UNUSED enum sample_format format,
- G_GNUC_UNUSED int volume)
-{
- return true;
-}
-
-struct decoder {
- const char *uri;
-
- const struct decoder_plugin *plugin;
-
- bool initialized;
-};
-
-void
-decoder_initialized(struct decoder *decoder,
- const struct audio_format *audio_format,
- G_GNUC_UNUSED bool seekable,
- G_GNUC_UNUSED float total_time)
-{
- struct audio_format_string af_string;
-
- assert(!decoder->initialized);
- assert(audio_format_valid(audio_format));
-
- g_printerr("audio_format=%s\n",
- audio_format_to_string(audio_format, &af_string));
-
- decoder->initialized = true;
-}
-
-enum decoder_command
-decoder_get_command(G_GNUC_UNUSED struct decoder *decoder)
-{
- return DECODE_COMMAND_NONE;
-}
-
-void decoder_command_finished(G_GNUC_UNUSED struct decoder *decoder)
-{
-}
-
-double decoder_seek_where(G_GNUC_UNUSED struct decoder *decoder)
-{
- return 1.0;
-}
-
-void decoder_seek_error(G_GNUC_UNUSED struct decoder *decoder)
-{
-}
-
-size_t
-decoder_read(G_GNUC_UNUSED struct decoder *decoder,
- struct input_stream *is,
- void *buffer, size_t length)
-{
- return input_stream_lock_read(is, buffer, length, NULL);
-}
-
-void
-decoder_timestamp(G_GNUC_UNUSED struct decoder *decoder,
- G_GNUC_UNUSED double t)
-{
-}
-
-enum decoder_command
-decoder_data(G_GNUC_UNUSED struct decoder *decoder,
- G_GNUC_UNUSED struct input_stream *is,
- const void *data, size_t datalen,
- G_GNUC_UNUSED uint16_t kbit_rate)
-{
- G_GNUC_UNUSED ssize_t nbytes = write(1, data, datalen);
- return DECODE_COMMAND_NONE;
-}
-
-enum decoder_command
-decoder_tag(G_GNUC_UNUSED struct decoder *decoder,
- G_GNUC_UNUSED struct input_stream *is,
- G_GNUC_UNUSED const struct tag *tag)
-{
- return DECODE_COMMAND_NONE;
-}
-
-float
-decoder_replay_gain(G_GNUC_UNUSED struct decoder *decoder,
- const struct replay_gain_info *replay_gain_info)
-{
- const struct replay_gain_tuple *tuple =
- &replay_gain_info->tuples[REPLAY_GAIN_ALBUM];
- if (replay_gain_tuple_defined(tuple))
- g_printerr("replay_gain[album]: gain=%f peak=%f\n",
- tuple->gain, tuple->peak);
-
- tuple = &replay_gain_info->tuples[REPLAY_GAIN_TRACK];
- if (replay_gain_tuple_defined(tuple))
- g_printerr("replay_gain[track]: gain=%f peak=%f\n",
- tuple->gain, tuple->peak);
-
- return 0.0;
-}
-
-void
-decoder_mixramp(G_GNUC_UNUSED struct decoder *decoder,
- G_GNUC_UNUSED float replay_gain_db,
- char *mixramp_start, char *mixramp_end)
-{
- g_free(mixramp_start);
- g_free(mixramp_end);
-}
-
-int main(int argc, char **argv)
-{
- GError *error = NULL;
- const char *decoder_name;
- struct decoder decoder;
-
- if (argc != 3) {
- g_printerr("Usage: run_decoder DECODER URI >OUT\n");
- return 1;
- }
-
- decoder_name = argv[1];
- decoder.uri = argv[2];
-
- g_thread_init(NULL);
- g_log_set_default_handler(my_log_func, NULL);
-
- io_thread_init();
- if (!io_thread_start(&error)) {
- g_warning("%s", error->message);
- g_error_free(error);
- return EXIT_FAILURE;
- }
-
- tag_pool_init();
-
- if (!input_stream_global_init(&error)) {
- g_warning("%s", error->message);
- g_error_free(error);
- return 2;
- }
-
- 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;
- }
-
- decoder.initialized = false;
-
- if (decoder.plugin->file_decode != NULL) {
- decoder_plugin_file_decode(decoder.plugin, &decoder,
- decoder.uri);
- } else if (decoder.plugin->stream_decode != NULL) {
- GMutex *mutex = g_mutex_new();
- GCond *cond = g_cond_new();
-
- struct input_stream *is =
- input_stream_open(decoder.uri, mutex, cond, &error);
- if (is == NULL) {
- if (error != NULL) {
- g_warning("%s", error->message);
- g_error_free(error);
- } else
- g_printerr("input_stream_open() failed\n");
-
- return 1;
- }
-
- decoder_plugin_stream_decode(decoder.plugin, &decoder, is);
-
- input_stream_close(is);
-
- g_cond_free(cond);
- g_mutex_free(mutex);
- } else {
- g_printerr("Decoder plugin is not usable\n");
- return 1;
- }
-
- decoder_plugin_deinit_all();
- input_stream_global_finish();
- io_thread_deinit();
-
- if (!decoder.initialized) {
- g_printerr("Decoding failed\n");
- return 1;
- }
-
- tag_pool_deinit();
-
- return 0;
-}
diff --git a/test/run_decoder.cxx b/test/run_decoder.cxx
new file mode 100644
index 000000000..a5826b278
--- /dev/null
+++ b/test/run_decoder.cxx
@@ -0,0 +1,225 @@
+/*
+ * 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.
+ */
+
+#include "config.h"
+#include "IOThread.hxx"
+#include "DecoderList.hxx"
+#include "DecoderAPI.hxx"
+#include "InputInit.hxx"
+#include "input_stream.h"
+#include "AudioFormat.hxx"
+#include "stdbin.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+static void
+my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level,
+ const gchar *message, G_GNUC_UNUSED gpointer user_data)
+{
+ if (log_domain != NULL)
+ g_printerr("%s: %s\n", log_domain, message);
+ else
+ g_printerr("%s\n", message);
+}
+
+struct decoder {
+ const char *uri;
+
+ const struct decoder_plugin *plugin;
+
+ bool initialized;
+};
+
+void
+decoder_initialized(struct decoder *decoder,
+ const AudioFormat audio_format,
+ G_GNUC_UNUSED bool seekable,
+ G_GNUC_UNUSED float total_time)
+{
+ struct audio_format_string af_string;
+
+ assert(!decoder->initialized);
+ assert(audio_format.IsValid());
+
+ g_printerr("audio_format=%s\n",
+ audio_format_to_string(audio_format, &af_string));
+
+ decoder->initialized = true;
+}
+
+enum decoder_command
+decoder_get_command(G_GNUC_UNUSED struct decoder *decoder)
+{
+ return DECODE_COMMAND_NONE;
+}
+
+void decoder_command_finished(G_GNUC_UNUSED struct decoder *decoder)
+{
+}
+
+double decoder_seek_where(G_GNUC_UNUSED struct decoder *decoder)
+{
+ return 1.0;
+}
+
+void decoder_seek_error(G_GNUC_UNUSED struct decoder *decoder)
+{
+}
+
+size_t
+decoder_read(G_GNUC_UNUSED struct decoder *decoder,
+ struct input_stream *is,
+ void *buffer, size_t length)
+{
+ return input_stream_lock_read(is, buffer, length, NULL);
+}
+
+void
+decoder_timestamp(G_GNUC_UNUSED struct decoder *decoder,
+ G_GNUC_UNUSED double t)
+{
+}
+
+enum decoder_command
+decoder_data(G_GNUC_UNUSED struct decoder *decoder,
+ G_GNUC_UNUSED struct input_stream *is,
+ const void *data, size_t datalen,
+ G_GNUC_UNUSED uint16_t kbit_rate)
+{
+ G_GNUC_UNUSED ssize_t nbytes = write(1, data, datalen);
+ return DECODE_COMMAND_NONE;
+}
+
+enum decoder_command
+decoder_tag(G_GNUC_UNUSED struct decoder *decoder,
+ G_GNUC_UNUSED struct input_stream *is,
+ G_GNUC_UNUSED Tag &&tag)
+{
+ return DECODE_COMMAND_NONE;
+}
+
+void
+decoder_replay_gain(G_GNUC_UNUSED struct decoder *decoder,
+ const struct replay_gain_info *replay_gain_info)
+{
+ const struct replay_gain_tuple *tuple =
+ &replay_gain_info->tuples[REPLAY_GAIN_ALBUM];
+ if (replay_gain_tuple_defined(tuple))
+ g_printerr("replay_gain[album]: gain=%f peak=%f\n",
+ tuple->gain, tuple->peak);
+
+ tuple = &replay_gain_info->tuples[REPLAY_GAIN_TRACK];
+ if (replay_gain_tuple_defined(tuple))
+ g_printerr("replay_gain[track]: gain=%f peak=%f\n",
+ tuple->gain, tuple->peak);
+}
+
+void
+decoder_mixramp(G_GNUC_UNUSED struct decoder *decoder,
+ char *mixramp_start, char *mixramp_end)
+{
+ g_free(mixramp_start);
+ g_free(mixramp_end);
+}
+
+int main(int argc, char **argv)
+{
+ GError *error = NULL;
+ const char *decoder_name;
+ struct decoder decoder;
+
+ if (argc != 3) {
+ g_printerr("Usage: run_decoder DECODER URI >OUT\n");
+ return 1;
+ }
+
+ decoder_name = argv[1];
+ decoder.uri = argv[2];
+
+#if !GLIB_CHECK_VERSION(2,32,0)
+ g_thread_init(NULL);
+#endif
+
+ g_log_set_default_handler(my_log_func, NULL);
+
+ io_thread_init();
+ if (!io_thread_start(&error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
+ if (!input_stream_global_init(&error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return 2;
+ }
+
+ 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;
+ }
+
+ decoder.initialized = false;
+
+ if (decoder.plugin->file_decode != NULL) {
+ decoder_plugin_file_decode(decoder.plugin, &decoder,
+ decoder.uri);
+ } else if (decoder.plugin->stream_decode != NULL) {
+ Mutex mutex;
+ Cond cond;
+
+ struct input_stream *is =
+ input_stream_open(decoder.uri, mutex, cond, &error);
+ if (is == NULL) {
+ if (error != NULL) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ } else
+ g_printerr("input_stream_open() failed\n");
+
+ return 1;
+ }
+
+ decoder_plugin_stream_decode(decoder.plugin, &decoder, is);
+
+ input_stream_close(is);
+ } else {
+ g_printerr("Decoder plugin is not usable\n");
+ return 1;
+ }
+
+ decoder_plugin_deinit_all();
+ input_stream_global_finish();
+ io_thread_deinit();
+
+ if (!decoder.initialized) {
+ g_printerr("Decoding failed\n");
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/test/run_encoder.c b/test/run_encoder.c
deleted file mode 100644
index db4d3af9b..000000000
--- a/test/run_encoder.c
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "encoder_list.h"
-#include "encoder_plugin.h"
-#include "audio_format.h"
-#include "audio_parser.h"
-#include "conf.h"
-#include "stdbin.h"
-
-#include <glib.h>
-
-#include <stddef.h>
-#include <unistd.h>
-
-static void
-encoder_to_stdout(struct encoder *encoder)
-{
- size_t length;
- static char buffer[32768];
-
- while ((length = encoder_read(encoder, buffer, sizeof(buffer))) > 0) {
- G_GNUC_UNUSED ssize_t ignored = write(1, buffer, length);
- }
-}
-
-int main(int argc, char **argv)
-{
- GError *error = NULL;
- struct audio_format audio_format;
- bool ret;
- const char *encoder_name;
- const struct encoder_plugin *plugin;
- struct encoder *encoder;
- struct config_param *param;
- static char buffer[32768];
- ssize_t nbytes;
-
- /* parse command line */
-
- if (argc > 3) {
- g_printerr("Usage: run_encoder [ENCODER] [FORMAT] <IN >OUT\n");
- return 1;
- }
-
- if (argc > 1)
- encoder_name = argv[1];
- else
- encoder_name = "vorbis";
-
- audio_format_init(&audio_format, 44100, SAMPLE_FORMAT_S16, 2);
-
- /* create the encoder */
-
- plugin = encoder_plugin_get(encoder_name);
- if (plugin == NULL) {
- g_printerr("No such encoder: %s\n", encoder_name);
- return 1;
- }
-
- param = config_new_param(NULL, -1);
- config_add_block_param(param, "quality", "5.0", -1);
-
- encoder = encoder_init(plugin, param, &error);
- if (encoder == NULL) {
- g_printerr("Failed to initialize encoder: %s\n",
- error->message);
- g_error_free(error);
- return 1;
- }
-
- /* open the encoder */
-
- if (argc > 2) {
- ret = audio_format_parse(&audio_format, argv[2],
- false, &error);
- if (!ret) {
- g_printerr("Failed to parse audio format: %s\n",
- error->message);
- g_error_free(error);
- return 1;
- }
- }
-
- if (!encoder_open(encoder, &audio_format, &error)) {
- g_printerr("Failed to open encoder: %s\n",
- error->message);
- g_error_free(error);
- return 1;
- }
-
- encoder_to_stdout(encoder);
-
- /* do it */
-
- while ((nbytes = read(0, buffer, sizeof(buffer))) > 0) {
- ret = encoder_write(encoder, buffer, nbytes, &error);
- if (!ret) {
- g_printerr("encoder_write() failed: %s\n",
- error->message);
- g_error_free(error);
- return 1;
- }
-
- encoder_to_stdout(encoder);
- }
-
- ret = encoder_end(encoder, &error);
- if (!ret) {
- g_printerr("encoder_flush() failed: %s\n",
- error->message);
- g_error_free(error);
- return 1;
- }
-
- encoder_to_stdout(encoder);
-}
diff --git a/test/run_encoder.cxx b/test/run_encoder.cxx
new file mode 100644
index 000000000..27ffddedd
--- /dev/null
+++ b/test/run_encoder.cxx
@@ -0,0 +1,129 @@
+/*
+ * 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 "EncoderList.hxx"
+#include "EncoderPlugin.hxx"
+#include "AudioFormat.hxx"
+#include "AudioParser.hxx"
+#include "conf.h"
+#include "stdbin.h"
+
+#include <glib.h>
+
+#include <stddef.h>
+#include <unistd.h>
+
+static void
+encoder_to_stdout(Encoder &encoder)
+{
+ size_t length;
+ static char buffer[32768];
+
+ while ((length = encoder_read(&encoder, buffer, sizeof(buffer))) > 0) {
+ G_GNUC_UNUSED ssize_t ignored = write(1, buffer, length);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ GError *error = NULL;
+ bool ret;
+ const char *encoder_name;
+ static char buffer[32768];
+
+ /* parse command line */
+
+ if (argc > 3) {
+ g_printerr("Usage: run_encoder [ENCODER] [FORMAT] <IN >OUT\n");
+ return 1;
+ }
+
+ if (argc > 1)
+ encoder_name = argv[1];
+ else
+ encoder_name = "vorbis";
+
+ /* create the encoder */
+
+ const auto plugin = encoder_plugin_get(encoder_name);
+ if (plugin == NULL) {
+ g_printerr("No such encoder: %s\n", encoder_name);
+ return 1;
+ }
+
+ config_param param;
+ param.AddBlockParam("quality", "5.0", -1);
+
+ const auto encoder = encoder_init(*plugin, param, &error);
+ if (encoder == NULL) {
+ g_printerr("Failed to initialize encoder: %s\n",
+ error->message);
+ g_error_free(error);
+ return 1;
+ }
+
+ /* open the encoder */
+
+ AudioFormat audio_format(44100, SampleFormat::S16, 2);
+ if (argc > 2) {
+ ret = audio_format_parse(audio_format, argv[2],
+ false, &error);
+ if (!ret) {
+ g_printerr("Failed to parse audio format: %s\n",
+ error->message);
+ g_error_free(error);
+ return 1;
+ }
+ }
+
+ if (!encoder_open(encoder, audio_format, &error)) {
+ g_printerr("Failed to open encoder: %s\n",
+ error->message);
+ g_error_free(error);
+ return 1;
+ }
+
+ encoder_to_stdout(*encoder);
+
+ /* do it */
+
+ ssize_t nbytes;
+ while ((nbytes = read(0, buffer, sizeof(buffer))) > 0) {
+ ret = encoder_write(encoder, buffer, nbytes, &error);
+ if (!ret) {
+ g_printerr("encoder_write() failed: %s\n",
+ error->message);
+ g_error_free(error);
+ return 1;
+ }
+
+ encoder_to_stdout(*encoder);
+ }
+
+ ret = encoder_end(encoder, &error);
+ if (!ret) {
+ g_printerr("encoder_flush() failed: %s\n",
+ error->message);
+ g_error_free(error);
+ return 1;
+ }
+
+ encoder_to_stdout(*encoder);
+}
diff --git a/test/run_filter.c b/test/run_filter.c
deleted file mode 100644
index 8e793b768..000000000
--- a/test/run_filter.c
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "conf.h"
-#include "audio_parser.h"
-#include "audio_format.h"
-#include "filter_plugin.h"
-#include "pcm_volume.h"
-#include "idle.h"
-#include "mixer_control.h"
-#include "playlist.h"
-#include "stdbin.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-#include <errno.h>
-#include <unistd.h>
-
-struct playlist g_playlist;
-
-void
-idle_add(G_GNUC_UNUSED unsigned flags)
-{
-}
-
-bool
-mixer_set_volume(G_GNUC_UNUSED struct mixer *mixer,
- G_GNUC_UNUSED unsigned volume, G_GNUC_UNUSED GError **error_r)
-{
- return true;
-}
-
-static void
-my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level,
- const gchar *message, G_GNUC_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(const char *block, const char *name)
-{
- const struct config_param *param = NULL;
-
- while ((param = config_get_next_param(block, param)) != NULL) {
- const char *current_name =
- config_get_block_string(param, "name", NULL);
- if (current_name != NULL && strcmp(current_name, name) == 0)
- return param;
- }
-
- return NULL;
-}
-
-static struct filter *
-load_filter(const char *name)
-{
- const struct config_param *param;
- struct filter *filter;
- GError *error = NULL;
-
- param = find_named_config_block("filter", name);
- if (param == NULL) {
- g_printerr("No such configured filter: %s\n", name);
- return false;
- }
-
- filter = filter_configured_new(param, &error);
- if (filter == NULL) {
- g_printerr("Failed to load filter: %s\n", error->message);
- g_error_free(error);
- return NULL;
- }
-
- return filter;
-}
-
-int main(int argc, char **argv)
-{
- struct audio_format audio_format;
- struct audio_format_string af_string;
- bool success;
- GError *error = NULL;
- struct filter *filter;
- const struct audio_format *out_audio_format;
- char buffer[4096];
-
- if (argc < 3 || argc > 4) {
- g_printerr("Usage: run_filter CONFIG NAME [FORMAT] <IN\n");
- return 1;
- }
-
- audio_format_init(&audio_format, 44100, SAMPLE_FORMAT_S16, 2);
-
- /* initialize GLib */
-
- g_thread_init(NULL);
- g_log_set_default_handler(my_log_func, NULL);
-
- /* read configuration file (mpd.conf) */
-
- config_global_init();
- success = config_read_file(argv[1], &error);
- if (!success) {
- g_printerr("%s:", error->message);
- g_error_free(error);
- return 1;
- }
-
- /* parse the audio format */
-
- if (argc > 3) {
- success = audio_format_parse(&audio_format, argv[3],
- false, &error);
- if (!success) {
- g_printerr("Failed to parse audio format: %s\n",
- error->message);
- g_error_free(error);
- return 1;
- }
- }
-
- /* initialize the filter */
-
- filter = load_filter(argv[2]);
- if (filter == NULL)
- return 1;
-
- /* open the filter */
-
- out_audio_format = filter_open(filter, &audio_format, &error);
- if (out_audio_format == NULL) {
- g_printerr("Failed to open filter: %s\n", error->message);
- g_error_free(error);
- filter_free(filter);
- return 1;
- }
-
- g_printerr("audio_format=%s\n",
- audio_format_to_string(out_audio_format, &af_string));
-
- /* play */
-
- while (true) {
- ssize_t nbytes;
- size_t length;
- const void *dest;
-
- nbytes = read(0, buffer, sizeof(buffer));
- if (nbytes <= 0)
- break;
-
- dest = filter_filter(filter, buffer, (size_t)nbytes,
- &length, &error);
- if (dest == NULL) {
- g_printerr("Filter failed: %s\n", error->message);
- filter_close(filter);
- filter_free(filter);
- return 1;
- }
-
- nbytes = write(1, dest, length);
- if (nbytes < 0) {
- g_printerr("Failed to write: %s\n", g_strerror(errno));
- filter_close(filter);
- filter_free(filter);
- return 1;
- }
- }
-
- /* cleanup and exit */
-
- filter_close(filter);
- filter_free(filter);
-
- config_global_finish();
-
- return 0;
-}
diff --git a/test/run_filter.cxx b/test/run_filter.cxx
new file mode 100644
index 000000000..761bc14e8
--- /dev/null
+++ b/test/run_filter.cxx
@@ -0,0 +1,194 @@
+/*
+ * 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 "conf.h"
+#include "fs/Path.hxx"
+#include "AudioParser.hxx"
+#include "AudioFormat.hxx"
+#include "FilterPlugin.hxx"
+#include "FilterInternal.hxx"
+#include "pcm/PcmVolume.hxx"
+#include "MixerControl.hxx"
+#include "stdbin.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+
+bool
+mixer_set_volume(gcc_unused Mixer *mixer,
+ G_GNUC_UNUSED unsigned volume, G_GNUC_UNUSED GError **error_r)
+{
+ return true;
+}
+
+static void
+my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level,
+ const gchar *message, G_GNUC_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)
+{
+ const struct config_param *param = NULL;
+
+ while ((param = config_get_next_param(option, param)) != NULL) {
+ const char *current_name = param->GetBlockValue("name");
+ if (current_name != NULL && strcmp(current_name, name) == 0)
+ return param;
+ }
+
+ return NULL;
+}
+
+static Filter *
+load_filter(const char *name)
+{
+ const struct config_param *param;
+ GError *error = NULL;
+
+ param = find_named_config_block(CONF_AUDIO_FILTER, name);
+ if (param == NULL) {
+ g_printerr("No such configured filter: %s\n", name);
+ return nullptr;
+ }
+
+ Filter *filter = filter_configured_new(*param, &error);
+ if (filter == NULL) {
+ g_printerr("Failed to load filter: %s\n", error->message);
+ g_error_free(error);
+ return NULL;
+ }
+
+ return filter;
+}
+
+int main(int argc, char **argv)
+{
+ struct audio_format_string af_string;
+ bool success;
+ GError *error = NULL;
+ char buffer[4096];
+
+ if (argc < 3 || argc > 4) {
+ g_printerr("Usage: run_filter CONFIG NAME [FORMAT] <IN\n");
+ return 1;
+ }
+
+ const Path config_path = Path::FromFS(argv[1]);
+
+ AudioFormat audio_format(44100, SampleFormat::S16, 2);
+
+ /* initialize GLib */
+
+#if !GLIB_CHECK_VERSION(2,32,0)
+ g_thread_init(NULL);
+#endif
+
+ g_log_set_default_handler(my_log_func, NULL);
+
+ /* read configuration file (mpd.conf) */
+
+ config_global_init();
+ if (!ReadConfigFile(config_path, &error)) {
+ g_printerr("%s:", error->message);
+ g_error_free(error);
+ return 1;
+ }
+
+ /* parse the audio format */
+
+ if (argc > 3) {
+ success = audio_format_parse(audio_format, argv[3],
+ false, &error);
+ if (!success) {
+ g_printerr("Failed to parse audio format: %s\n",
+ error->message);
+ g_error_free(error);
+ return 1;
+ }
+ }
+
+ /* initialize the filter */
+
+ Filter *filter = load_filter(argv[2]);
+ if (filter == NULL)
+ return 1;
+
+ /* open the filter */
+
+ const AudioFormat out_audio_format =
+ filter->Open(audio_format, &error);
+ if (!out_audio_format.IsDefined()) {
+ g_printerr("Failed to open filter: %s\n", error->message);
+ g_error_free(error);
+ delete filter;
+ return 1;
+ }
+
+ g_printerr("audio_format=%s\n",
+ audio_format_to_string(out_audio_format, &af_string));
+
+ /* play */
+
+ while (true) {
+ ssize_t nbytes;
+ size_t length;
+ const void *dest;
+
+ nbytes = read(0, buffer, sizeof(buffer));
+ if (nbytes <= 0)
+ break;
+
+ dest = filter->FilterPCM(buffer, (size_t)nbytes,
+ &length, &error);
+ if (dest == NULL) {
+ g_printerr("Filter failed: %s\n", error->message);
+ filter->Close();
+ delete filter;
+ return 1;
+ }
+
+ nbytes = write(1, dest, length);
+ if (nbytes < 0) {
+ g_printerr("Failed to write: %s\n", g_strerror(errno));
+ filter->Close();
+ delete filter;
+ return 1;
+ }
+ }
+
+ /* cleanup and exit */
+
+ filter->Close();
+ delete filter;
+
+ config_global_finish();
+
+ return 0;
+}
diff --git a/test/run_inotify.c b/test/run_inotify.c
deleted file mode 100644
index 3e7c70dba..000000000
--- a/test/run_inotify.c
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "inotify_source.h"
-
-#include <stdbool.h>
-#include <sys/inotify.h>
-#include <signal.h>
-
-static GMainLoop *main_loop;
-
-static void
-exit_signal_handler(G_GNUC_UNUSED int signum)
-{
- g_main_loop_quit(main_loop);
-}
-
-enum {
- IN_MASK = IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_DELETE_SELF
- |IN_MOVE|IN_MOVE_SELF
-#ifdef IN_ONLYDIR
- |IN_ONLYDIR
-#endif
-};
-
-static void
-my_inotify_callback(G_GNUC_UNUSED int wd, unsigned mask,
- const char *name, G_GNUC_UNUSED void *ctx)
-{
- g_print("mask=0x%x name='%s'\n", mask, name);
-}
-
-int main(int argc, char **argv)
-{
- GError *error = NULL;
- const char *path;
-
- if (argc != 2) {
- g_printerr("Usage: run_inotify PATH\n");
- return 1;
- }
-
- path = argv[1];
-
- struct mpd_inotify_source *source =
- mpd_inotify_source_new(my_inotify_callback, NULL,
- &error);
- if (source == NULL) {
- g_warning("%s", error->message);
- g_error_free(error);
- return 2;
- }
-
- int descriptor = mpd_inotify_source_add(source, path,
- IN_MASK, &error);
- if (descriptor < 0) {
- mpd_inotify_source_free(source);
- g_warning("%s", error->message);
- g_error_free(error);
- return 2;
- }
-
- main_loop = g_main_loop_new(NULL, false);
-
- struct sigaction sa;
- sa.sa_flags = 0;
- sigemptyset(&sa.sa_mask);
- sa.sa_handler = exit_signal_handler;
- sigaction(SIGINT, &sa, NULL);
- sigaction(SIGTERM, &sa, NULL);
-
- g_main_loop_run(main_loop);
- g_main_loop_unref(main_loop);
-
- mpd_inotify_source_free(source);
-}
diff --git a/test/run_inotify.cxx b/test/run_inotify.cxx
new file mode 100644
index 000000000..29fcf70ab
--- /dev/null
+++ b/test/run_inotify.cxx
@@ -0,0 +1,94 @@
+/*
+ * 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.
+ */
+
+#include "config.h"
+#include "InotifySource.hxx"
+#include "event/Loop.hxx"
+
+#include <glib.h>
+
+#include <sys/inotify.h>
+#include <signal.h>
+
+static EventLoop *event_loop;
+
+static void
+exit_signal_handler(G_GNUC_UNUSED int signum)
+{
+ event_loop->Break();
+}
+
+enum {
+ IN_MASK = IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_DELETE_SELF
+ |IN_MOVE|IN_MOVE_SELF
+#ifdef IN_ONLYDIR
+ |IN_ONLYDIR
+#endif
+};
+
+static void
+my_inotify_callback(G_GNUC_UNUSED int wd, unsigned mask,
+ const char *name, G_GNUC_UNUSED void *ctx)
+{
+ g_print("mask=0x%x name='%s'\n", mask, name);
+}
+
+int main(int argc, char **argv)
+{
+ GError *error = NULL;
+ const char *path;
+
+ if (argc != 2) {
+ g_printerr("Usage: run_inotify PATH\n");
+ return 1;
+ }
+
+ path = argv[1];
+
+ event_loop = new EventLoop(EventLoop::Default());
+
+ InotifySource *source = InotifySource::Create(*event_loop,
+ my_inotify_callback,
+ nullptr, &error);
+ if (source == NULL) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return 2;
+ }
+
+ int descriptor = source->Add(path, IN_MASK, &error);
+ if (descriptor < 0) {
+ delete source;
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return 2;
+ }
+
+ struct sigaction sa;
+ sa.sa_flags = 0;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_handler = exit_signal_handler;
+ sigaction(SIGINT, &sa, NULL);
+ sigaction(SIGTERM, &sa, NULL);
+
+ event_loop->Run();
+
+ delete source;
+ delete event_loop;
+}
diff --git a/test/run_input.c b/test/run_input.c
deleted file mode 100644
index 676e4e618..000000000
--- a/test/run_input.c
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "io_thread.h"
-#include "input_init.h"
-#include "input_stream.h"
-#include "tag_pool.h"
-#include "tag_save.h"
-#include "conf.h"
-#include "stdbin.h"
-
-#ifdef ENABLE_ARCHIVE
-#include "archive_list.h"
-#endif
-
-#include <glib.h>
-
-#include <unistd.h>
-#include <stdlib.h>
-
-static void
-my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level,
- const gchar *message, G_GNUC_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(struct input_stream *is)
-{
- GError *error = NULL;
- char buffer[4096];
- size_t num_read;
- ssize_t num_written;
-
- input_stream_lock(is);
-
- /* wait until the stream becomes ready */
-
- input_stream_wait_ready(is);
-
- if (!input_stream_check(is, &error)) {
- g_warning("%s", error->message);
- g_error_free(error);
- input_stream_unlock(is);
- return EXIT_FAILURE;
- }
-
- /* print meta data */
-
- if (is->mime != NULL)
- g_printerr("MIME type: %s\n", is->mime);
-
- /* read data and tags from the stream */
-
- while (!input_stream_eof(is)) {
- struct tag *tag = input_stream_tag(is);
- if (tag != NULL) {
- g_printerr("Received a tag:\n");
- tag_save(stderr, tag);
- tag_free(tag);
- }
-
- num_read = input_stream_read(is, buffer, sizeof(buffer),
- &error);
- if (num_read == 0) {
- if (error != NULL) {
- g_warning("%s", error->message);
- g_error_free(error);
- }
-
- break;
- }
-
- num_written = write(1, buffer, num_read);
- if (num_written <= 0)
- break;
- }
-
- if (!input_stream_check(is, &error)) {
- g_warning("%s", error->message);
- g_error_free(error);
- input_stream_unlock(is);
- return EXIT_FAILURE;
- }
-
- input_stream_unlock(is);
-
- return 0;
-}
-
-int main(int argc, char **argv)
-{
- GError *error = NULL;
- struct input_stream *is;
- int ret;
-
- if (argc != 2) {
- g_printerr("Usage: run_input URI\n");
- return 1;
- }
-
- /* initialize GLib */
-
- g_thread_init(NULL);
- g_log_set_default_handler(my_log_func, NULL);
-
- /* initialize MPD */
-
- tag_pool_init();
- config_global_init();
-
- io_thread_init();
- if (!io_thread_start(&error)) {
- g_warning("%s", error->message);
- g_error_free(error);
- return EXIT_FAILURE;
- }
-
-#ifdef ENABLE_ARCHIVE
- archive_plugin_init_all();
-#endif
-
- if (!input_stream_global_init(&error)) {
- g_warning("%s", error->message);
- g_error_free(error);
- return 2;
- }
-
- /* open the stream and dump it */
-
- GMutex *mutex = g_mutex_new();
- GCond *cond = g_cond_new();
-
- is = input_stream_open(argv[1], mutex, cond, &error);
- if (is != NULL) {
- ret = dump_input_stream(is);
- input_stream_close(is);
- } else {
- if (error != NULL) {
- g_warning("%s", error->message);
- g_error_free(error);
- } else
- g_printerr("input_stream_open() failed\n");
- ret = 2;
- }
-
- g_cond_free(cond);
- g_mutex_free(mutex);
-
- /* deinitialize everything */
-
- input_stream_global_finish();
-
-#ifdef ENABLE_ARCHIVE
- archive_plugin_deinit_all();
-#endif
-
- io_thread_deinit();
-
- config_global_finish();
- tag_pool_deinit();
-
- return ret;
-}
diff --git a/test/run_input.cxx b/test/run_input.cxx
new file mode 100644
index 000000000..e93e817d3
--- /dev/null
+++ b/test/run_input.cxx
@@ -0,0 +1,184 @@
+/*
+ * 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 "TagSave.hxx"
+#include "stdbin.h"
+#include "Tag.hxx"
+#include "conf.h"
+#include "input_stream.h"
+#include "InputStream.hxx"
+#include "InputInit.hxx"
+#include "IOThread.hxx"
+
+#ifdef ENABLE_ARCHIVE
+#include "ArchiveList.hxx"
+#endif
+
+#include <glib.h>
+
+#include <unistd.h>
+#include <stdlib.h>
+
+static void
+my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level,
+ const gchar *message, G_GNUC_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(struct input_stream *is)
+{
+ GError *error = NULL;
+ char buffer[4096];
+ size_t num_read;
+ ssize_t num_written;
+
+ input_stream_lock(is);
+
+ /* wait until the stream becomes ready */
+
+ input_stream_wait_ready(is);
+
+ if (!input_stream_check(is, &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ input_stream_unlock(is);
+ return EXIT_FAILURE;
+ }
+
+ /* print meta data */
+
+ if (!is->mime.empty())
+ g_printerr("MIME type: %s\n", is->mime.c_str());
+
+ /* read data and tags from the stream */
+
+ while (!input_stream_eof(is)) {
+ Tag *tag = input_stream_tag(is);
+ if (tag != NULL) {
+ g_printerr("Received a tag:\n");
+ tag_save(stderr, *tag);
+ delete tag;
+ }
+
+ num_read = input_stream_read(is, buffer, sizeof(buffer),
+ &error);
+ if (num_read == 0) {
+ if (error != NULL) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ }
+
+ break;
+ }
+
+ num_written = write(1, buffer, num_read);
+ if (num_written <= 0)
+ break;
+ }
+
+ if (!input_stream_check(is, &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ input_stream_unlock(is);
+ return EXIT_FAILURE;
+ }
+
+ input_stream_unlock(is);
+
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ GError *error = NULL;
+ struct input_stream *is;
+ int ret;
+
+ if (argc != 2) {
+ g_printerr("Usage: run_input URI\n");
+ return 1;
+ }
+
+ /* initialize GLib */
+
+#if !GLIB_CHECK_VERSION(2,32,0)
+ g_thread_init(NULL);
+#endif
+
+ g_log_set_default_handler(my_log_func, NULL);
+
+ /* initialize MPD */
+
+ config_global_init();
+
+ io_thread_init();
+ if (!io_thread_start(&error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
+#ifdef ENABLE_ARCHIVE
+ archive_plugin_init_all();
+#endif
+
+ if (!input_stream_global_init(&error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return 2;
+ }
+
+ /* open the stream and dump it */
+
+ Mutex mutex;
+ Cond cond;
+
+ is = input_stream_open(argv[1], mutex, cond, &error);
+ if (is != NULL) {
+ ret = dump_input_stream(is);
+ input_stream_close(is);
+ } else {
+ if (error != NULL) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ } else
+ g_printerr("input_stream_open() failed\n");
+ ret = 2;
+ }
+
+ /* deinitialize everything */
+
+ input_stream_global_finish();
+
+#ifdef ENABLE_ARCHIVE
+ archive_plugin_deinit_all();
+#endif
+
+ io_thread_deinit();
+
+ config_global_finish();
+
+ return ret;
+}
diff --git a/test/run_normalize.c b/test/run_normalize.c
deleted file mode 100644
index fc26829ed..000000000
--- a/test/run_normalize.c
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * This program is a command line interface to MPD's normalize library
- * (based on AudioCompress).
- *
- */
-
-#include "config.h"
-#include "AudioCompress/compress.h"
-#include "audio_parser.h"
-#include "audio_format.h"
-#include "stdbin.h"
-
-#include <glib.h>
-
-#include <stddef.h>
-#include <unistd.h>
-#include <string.h>
-
-int main(int argc, char **argv)
-{
- GError *error = NULL;
- struct audio_format audio_format;
- bool ret;
- struct Compressor *compressor;
- static char buffer[4096];
- ssize_t nbytes;
-
- if (argc > 2) {
- g_printerr("Usage: run_normalize [FORMAT] <IN >OUT\n");
- return 1;
- }
-
- if (argc > 1) {
- ret = audio_format_parse(&audio_format, argv[1],
- false, &error);
- if (!ret) {
- g_printerr("Failed to parse audio format: %s\n",
- error->message);
- return 1;
- }
- } else
- audio_format_init(&audio_format, 48000, 16, 2);
-
- compressor = Compressor_new(0);
-
- while ((nbytes = read(0, buffer, sizeof(buffer))) > 0) {
- Compressor_Process_int16(compressor,
- (int16_t *)buffer, nbytes / 2);
-
- G_GNUC_UNUSED ssize_t ignored = write(1, buffer, nbytes);
- }
-
- Compressor_delete(compressor);
-}
diff --git a/test/run_normalize.cxx b/test/run_normalize.cxx
new file mode 100644
index 000000000..72e23054f
--- /dev/null
+++ b/test/run_normalize.cxx
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+
+/*
+ * This program is a command line interface to MPD's normalize library
+ * (based on AudioCompress).
+ *
+ */
+
+#include "config.h"
+#include "AudioCompress/compress.h"
+#include "AudioParser.hxx"
+#include "AudioFormat.hxx"
+#include "stdbin.h"
+
+#include <glib.h>
+
+#include <stddef.h>
+#include <unistd.h>
+#include <string.h>
+
+int main(int argc, char **argv)
+{
+ GError *error = NULL;
+ bool ret;
+ struct Compressor *compressor;
+ static char buffer[4096];
+ ssize_t nbytes;
+
+ if (argc > 2) {
+ g_printerr("Usage: run_normalize [FORMAT] <IN >OUT\n");
+ return 1;
+ }
+
+ AudioFormat audio_format(48000, SampleFormat::S16, 2);
+ if (argc > 1) {
+ ret = audio_format_parse(audio_format, argv[1],
+ false, &error);
+ if (!ret) {
+ g_printerr("Failed to parse audio format: %s\n",
+ error->message);
+ return 1;
+ }
+ }
+
+ compressor = Compressor_new(0);
+
+ while ((nbytes = read(0, buffer, sizeof(buffer))) > 0) {
+ Compressor_Process_int16(compressor,
+ (int16_t *)buffer, nbytes / 2);
+
+ G_GNUC_UNUSED ssize_t ignored = write(1, buffer, nbytes);
+ }
+
+ Compressor_delete(compressor);
+}
diff --git a/test/run_output.c b/test/run_output.c
deleted file mode 100644
index bbb1be7d2..000000000
--- a/test/run_output.c
+++ /dev/null
@@ -1,253 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "io_thread.h"
-#include "output_plugin.h"
-#include "output_internal.h"
-#include "output_control.h"
-#include "conf.h"
-#include "audio_parser.h"
-#include "filter_registry.h"
-#include "pcm_convert.h"
-#include "event_pipe.h"
-#include "idle.h"
-#include "playlist.h"
-#include "player_control.h"
-#include "stdbin.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-#include <unistd.h>
-#include <stdlib.h>
-
-struct playlist g_playlist;
-
-void
-idle_add(G_GNUC_UNUSED unsigned flags)
-{
-}
-
-void
-event_pipe_emit(G_GNUC_UNUSED enum pipe_event event)
-{
-}
-
-void pcm_convert_init(G_GNUC_UNUSED struct pcm_convert_state *state)
-{
-}
-
-void pcm_convert_deinit(G_GNUC_UNUSED struct pcm_convert_state *state)
-{
-}
-
-const void *
-pcm_convert(G_GNUC_UNUSED struct pcm_convert_state *state,
- G_GNUC_UNUSED const struct audio_format *src_format,
- G_GNUC_UNUSED const void *src, G_GNUC_UNUSED size_t src_size,
- G_GNUC_UNUSED const struct audio_format *dest_format,
- G_GNUC_UNUSED size_t *dest_size_r,
- GError **error_r)
-{
- g_set_error(error_r, pcm_convert_quark(), 0,
- "Not implemented");
- return NULL;
-}
-
-const struct filter_plugin *
-filter_plugin_by_name(G_GNUC_UNUSED const char *name)
-{
- assert(false);
- return NULL;
-}
-
-static const struct config_param *
-find_named_config_block(const char *block, const char *name)
-{
- const struct config_param *param = NULL;
-
- while ((param = config_get_next_param(block, param)) != NULL) {
- const char *current_name =
- config_get_block_string(param, "name", NULL);
- if (current_name != NULL && strcmp(current_name, name) == 0)
- return param;
- }
-
- return NULL;
-}
-
-static struct audio_output *
-load_audio_output(const char *name)
-{
- const struct config_param *param;
- GError *error = NULL;
-
- param = find_named_config_block(CONF_AUDIO_OUTPUT, name);
- if (param == NULL) {
- g_printerr("No such configured audio output: %s\n", name);
- return false;
- }
-
- static struct player_control dummy_player_control;
-
- struct audio_output *ao =
- audio_output_new(param, &dummy_player_control, &error);
- if (ao == NULL) {
- g_printerr("%s\n", error->message);
- g_error_free(error);
- }
-
- return ao;
-}
-
-static bool
-run_output(struct audio_output *ao, struct audio_format *audio_format)
-{
- /* open the audio output */
-
- GError *error = NULL;
- if (!ao_plugin_enable(ao, &error)) {
- g_printerr("Failed to enable audio output: %s\n",
- error->message);
- g_error_free(error);
- return false;
- }
-
- if (!ao_plugin_open(ao, audio_format, &error)) {
- ao_plugin_disable(ao);
- g_printerr("Failed to open audio output: %s\n",
- error->message);
- g_error_free(error);
- return false;
- }
-
- struct audio_format_string af_string;
- g_printerr("audio_format=%s\n",
- audio_format_to_string(audio_format, &af_string));
-
- size_t frame_size = audio_format_frame_size(audio_format);
-
- /* play */
-
- size_t length = 0;
- char buffer[4096];
- while (true) {
- if (length < sizeof(buffer)) {
- ssize_t nbytes = read(0, buffer + length,
- sizeof(buffer) - length);
- if (nbytes <= 0)
- break;
-
- length += (size_t)nbytes;
- }
-
- size_t play_length = (length / frame_size) * frame_size;
- if (play_length > 0) {
- size_t consumed = ao_plugin_play(ao,
- buffer, play_length,
- &error);
- if (consumed == 0) {
- ao_plugin_close(ao);
- ao_plugin_disable(ao);
- g_printerr("Failed to play: %s\n",
- error->message);
- g_error_free(error);
- return false;
- }
-
- assert(consumed <= length);
- assert(consumed % frame_size == 0);
-
- length -= consumed;
- memmove(buffer, buffer + consumed, length);
- }
- }
-
- ao_plugin_close(ao);
- ao_plugin_disable(ao);
- return true;
-}
-
-int main(int argc, char **argv)
-{
- struct audio_format audio_format;
- bool success;
- GError *error = NULL;
-
- if (argc < 3 || argc > 4) {
- g_printerr("Usage: run_output CONFIG NAME [FORMAT] <IN\n");
- return 1;
- }
-
- audio_format_init(&audio_format, 44100, SAMPLE_FORMAT_S16, 2);
-
- g_thread_init(NULL);
-
- /* read configuration file (mpd.conf) */
-
- config_global_init();
- success = config_read_file(argv[1], &error);
- if (!success) {
- g_printerr("%s:", error->message);
- g_error_free(error);
- return 1;
- }
-
- io_thread_init();
- if (!io_thread_start(&error)) {
- g_warning("%s", error->message);
- g_error_free(error);
- return EXIT_FAILURE;
- }
-
- /* initialize the audio output */
-
- struct audio_output *ao = load_audio_output(argv[2]);
- if (ao == NULL)
- return 1;
-
- /* parse the audio format */
-
- if (argc > 3) {
- success = audio_format_parse(&audio_format, argv[3],
- false, &error);
- if (!success) {
- g_printerr("Failed to parse audio format: %s\n",
- error->message);
- g_error_free(error);
- return 1;
- }
- }
-
- /* do it */
-
- success = run_output(ao, &audio_format);
-
- /* cleanup and exit */
-
- audio_output_free(ao);
-
- io_thread_deinit();
-
- config_global_finish();
-
- return success ? EXIT_SUCCESS : EXIT_FAILURE;
-}
diff --git a/test/run_output.cxx b/test/run_output.cxx
new file mode 100644
index 000000000..fbdeebcb6
--- /dev/null
+++ b/test/run_output.cxx
@@ -0,0 +1,253 @@
+/*
+ * 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 "OutputControl.hxx"
+#include "OutputInternal.hxx"
+#include "OutputPlugin.hxx"
+#include "conf.h"
+#include "Idle.hxx"
+#include "Main.hxx"
+#include "event/Loop.hxx"
+#include "GlobalEvents.hxx"
+#include "IOThread.hxx"
+#include "fs/Path.hxx"
+#include "AudioParser.hxx"
+#include "pcm/PcmConvert.hxx"
+#include "FilterRegistry.hxx"
+#include "PlayerControl.hxx"
+#include "stdbin.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+EventLoop *main_loop;
+
+void
+GlobalEvents::Emit(gcc_unused Event event)
+{
+}
+
+PcmConvert::PcmConvert() {}
+PcmConvert::~PcmConvert() {}
+
+const void *
+PcmConvert::Convert(gcc_unused const AudioFormat src_format,
+ gcc_unused const void *src, gcc_unused size_t src_size,
+ gcc_unused const AudioFormat dest_format,
+ gcc_unused size_t *dest_size_r,
+ gcc_unused GError **error_r)
+{
+ g_set_error(error_r, pcm_convert_quark(), 0,
+ "Not implemented");
+ return NULL;
+}
+
+const struct filter_plugin *
+filter_plugin_by_name(G_GNUC_UNUSED const char *name)
+{
+ assert(false);
+ return NULL;
+}
+
+static const struct config_param *
+find_named_config_block(ConfigOption option, const char *name)
+{
+ const struct config_param *param = NULL;
+
+ while ((param = config_get_next_param(option, param)) != NULL) {
+ const char *current_name = param->GetBlockValue("name");
+ if (current_name != NULL && strcmp(current_name, name) == 0)
+ return param;
+ }
+
+ return NULL;
+}
+
+player_control::player_control(gcc_unused unsigned _buffer_chunks,
+ gcc_unused unsigned _buffered_before_play) {}
+player_control::~player_control() {}
+
+static struct audio_output *
+load_audio_output(const char *name)
+{
+ const struct config_param *param;
+ GError *error = NULL;
+
+ param = find_named_config_block(CONF_AUDIO_OUTPUT, name);
+ if (param == NULL) {
+ g_printerr("No such configured audio output: %s\n", name);
+ return nullptr;
+ }
+
+ static struct player_control dummy_player_control(32, 4);
+
+ struct audio_output *ao =
+ audio_output_new(*param, &dummy_player_control, &error);
+ if (ao == NULL) {
+ g_printerr("%s\n", error->message);
+ g_error_free(error);
+ }
+
+ return ao;
+}
+
+static bool
+run_output(struct audio_output *ao, AudioFormat audio_format)
+{
+ /* open the audio output */
+
+ GError *error = NULL;
+ if (!ao_plugin_enable(ao, &error)) {
+ g_printerr("Failed to enable audio output: %s\n",
+ error->message);
+ g_error_free(error);
+ return false;
+ }
+
+ if (!ao_plugin_open(ao, audio_format, &error)) {
+ ao_plugin_disable(ao);
+ g_printerr("Failed to open audio output: %s\n",
+ error->message);
+ g_error_free(error);
+ return false;
+ }
+
+ struct audio_format_string af_string;
+ g_printerr("audio_format=%s\n",
+ audio_format_to_string(audio_format, &af_string));
+
+ size_t frame_size = audio_format.GetFrameSize();
+
+ /* play */
+
+ size_t length = 0;
+ char buffer[4096];
+ while (true) {
+ if (length < sizeof(buffer)) {
+ ssize_t nbytes = read(0, buffer + length,
+ sizeof(buffer) - length);
+ if (nbytes <= 0)
+ break;
+
+ length += (size_t)nbytes;
+ }
+
+ size_t play_length = (length / frame_size) * frame_size;
+ if (play_length > 0) {
+ size_t consumed = ao_plugin_play(ao,
+ buffer, play_length,
+ &error);
+ if (consumed == 0) {
+ ao_plugin_close(ao);
+ ao_plugin_disable(ao);
+ g_printerr("Failed to play: %s\n",
+ error->message);
+ g_error_free(error);
+ return false;
+ }
+
+ assert(consumed <= length);
+ assert(consumed % frame_size == 0);
+
+ length -= consumed;
+ memmove(buffer, buffer + consumed, length);
+ }
+ }
+
+ ao_plugin_close(ao);
+ ao_plugin_disable(ao);
+ return true;
+}
+
+int main(int argc, char **argv)
+{
+ bool success;
+ GError *error = NULL;
+
+ if (argc < 3 || argc > 4) {
+ g_printerr("Usage: run_output CONFIG NAME [FORMAT] <IN\n");
+ return 1;
+ }
+
+ const Path config_path = Path::FromFS(argv[1]);
+
+ AudioFormat audio_format(44100, SampleFormat::S16, 2);
+
+#if !GLIB_CHECK_VERSION(2,32,0)
+ g_thread_init(NULL);
+#endif
+
+ /* read configuration file (mpd.conf) */
+
+ config_global_init();
+ if (!ReadConfigFile(config_path, &error)) {
+ g_printerr("%s:", error->message);
+ g_error_free(error);
+ return 1;
+ }
+
+ main_loop = new EventLoop(EventLoop::Default());
+
+ io_thread_init();
+ if (!io_thread_start(&error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
+ /* initialize the audio output */
+
+ struct audio_output *ao = load_audio_output(argv[2]);
+ if (ao == NULL)
+ return 1;
+
+ /* parse the audio format */
+
+ if (argc > 3) {
+ success = audio_format_parse(audio_format, argv[3],
+ false, &error);
+ if (!success) {
+ g_printerr("Failed to parse audio format: %s\n",
+ error->message);
+ g_error_free(error);
+ return 1;
+ }
+ }
+
+ /* do it */
+
+ success = run_output(ao, audio_format);
+
+ /* cleanup and exit */
+
+ audio_output_free(ao);
+
+ io_thread_deinit();
+
+ delete main_loop;
+
+ config_global_finish();
+
+ return success ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/test/run_tcp_connect.c b/test/run_tcp_connect.c
deleted file mode 100644
index bf8d9b82f..000000000
--- a/test/run_tcp_connect.c
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "resolver.h"
-#include "io_thread.h"
-#include "tcp_connect.h"
-#include "fd_util.h"
-
-#include <assert.h>
-#include <stdlib.h>
-
-#ifdef WIN32
-#include <ws2tcpip.h>
-#include <winsock.h>
-#else
-#include <sys/socket.h>
-#include <netdb.h>
-#endif
-
-static struct tcp_connect *handle;
-static GMutex *mutex;
-static GCond *cond;
-static bool done, success;
-
-static void
-my_tcp_connect_success(int fd, G_GNUC_UNUSED void *ctx)
-{
- assert(!done);
- assert(!success);
-
- close_socket(fd);
- g_print("success\n");
-
- g_mutex_lock(mutex);
- done = success = true;
- g_cond_signal(cond);
- g_mutex_unlock(mutex);
-}
-
-static void
-my_tcp_connect_error(GError *error, G_GNUC_UNUSED void *ctx)
-{
- assert(!done);
- assert(!success);
-
- g_printerr("error: %s\n", error->message);
- g_error_free(error);
-
- g_mutex_lock(mutex);
- done = true;
- g_cond_signal(cond);
- g_mutex_unlock(mutex);
-}
-
-static void
-my_tcp_connect_timeout(G_GNUC_UNUSED void *ctx)
-{
- assert(!done);
- assert(!success);
-
- g_printerr("timeout\n");
-
- g_mutex_lock(mutex);
- done = true;
- g_cond_signal(cond);
- g_mutex_unlock(mutex);
-}
-
-static void
-my_tcp_connect_canceled(G_GNUC_UNUSED void *ctx)
-{
- assert(!done);
- assert(!success);
-
- g_printerr("canceled\n");
-
- g_mutex_lock(mutex);
- done = true;
- g_cond_signal(cond);
- g_mutex_unlock(mutex);
-}
-
-static const struct tcp_connect_handler my_tcp_connect_handler = {
- .success = my_tcp_connect_success,
- .error = my_tcp_connect_error,
- .timeout = my_tcp_connect_timeout,
- .canceled = my_tcp_connect_canceled,
-};
-
-int main(int argc, char **argv)
-{
- if (argc != 2) {
- g_printerr("Usage: run_tcp_connect IP:PORT\n");
- return 1;
- }
-
- GError *error = NULL;
- struct addrinfo *ai = resolve_host_port(argv[1], 80, 0, SOCK_STREAM,
- &error);
- if (ai == NULL) {
- g_printerr("%s\n", error->message);
- g_error_free(error);
- return EXIT_FAILURE;
- }
-
- /* initialize GLib */
-
- g_thread_init(NULL);
-
- /* initialize MPD */
-
- io_thread_init();
- if (!io_thread_start(&error)) {
- freeaddrinfo(ai);
- g_printerr("%s", error->message);
- g_error_free(error);
- return EXIT_FAILURE;
- }
-
- /* open the connection */
-
- mutex = g_mutex_new();
- cond = g_cond_new();
-
- tcp_connect_address(ai->ai_addr, ai->ai_addrlen, 5000,
- &my_tcp_connect_handler, NULL,
- &handle);
- freeaddrinfo(ai);
-
- if (handle != NULL) {
- g_mutex_lock(mutex);
- while (!done)
- g_cond_wait(cond, mutex);
- g_mutex_unlock(mutex);
-
- tcp_connect_free(handle);
- }
-
- g_cond_free(cond);
- g_mutex_free(mutex);
-
- /* deinitialize everything */
-
- io_thread_deinit();
-
- return EXIT_SUCCESS;
-}
diff --git a/test/software_volume.c b/test/software_volume.c
deleted file mode 100644
index 2357da672..000000000
--- a/test/software_volume.c
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * This program is a command line interface to MPD's software volume
- * library (pcm_volume.c).
- *
- */
-
-#include "config.h"
-#include "pcm_volume.h"
-#include "audio_parser.h"
-#include "audio_format.h"
-#include "stdbin.h"
-
-#include <glib.h>
-
-#include <stddef.h>
-#include <unistd.h>
-
-int main(int argc, char **argv)
-{
- GError *error = NULL;
- struct audio_format audio_format;
- bool ret;
- static char buffer[4096];
- ssize_t nbytes;
-
- if (argc > 2) {
- g_printerr("Usage: software_volume [FORMAT] <IN >OUT\n");
- return 1;
- }
-
- if (argc > 1) {
- ret = audio_format_parse(&audio_format, argv[1],
- false, &error);
- if (!ret) {
- g_printerr("Failed to parse audio format: %s\n",
- error->message);
- return 1;
- }
- } else
- audio_format_init(&audio_format, 48000, SAMPLE_FORMAT_S16, 2);
-
- 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;
- }
-
- G_GNUC_UNUSED ssize_t ignored = write(1, buffer, nbytes);
- }
-}
diff --git a/test/software_volume.cxx b/test/software_volume.cxx
new file mode 100644
index 000000000..c46804731
--- /dev/null
+++ b/test/software_volume.cxx
@@ -0,0 +1,70 @@
+/*
+ * 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.
+ */
+
+/*
+ * This program is a command line interface to MPD's software volume
+ * library (pcm_volume.c).
+ *
+ */
+
+#include "config.h"
+#include "pcm/PcmVolume.hxx"
+#include "AudioParser.hxx"
+#include "AudioFormat.hxx"
+#include "stdbin.h"
+
+#include <glib.h>
+
+#include <stddef.h>
+#include <unistd.h>
+
+int main(int argc, char **argv)
+{
+ GError *error = NULL;
+ bool ret;
+ static char buffer[4096];
+ ssize_t nbytes;
+
+ if (argc > 2) {
+ g_printerr("Usage: software_volume [FORMAT] <IN >OUT\n");
+ return 1;
+ }
+
+ AudioFormat audio_format(48000, SampleFormat::S16, 2);
+ if (argc > 1) {
+ ret = audio_format_parse(audio_format, argv[1],
+ false, &error);
+ if (!ret) {
+ g_printerr("Failed to parse audio format: %s\n",
+ error->message);
+ return 1;
+ }
+ }
+
+ 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;
+ }
+
+ G_GNUC_UNUSED ssize_t ignored = write(1, buffer, nbytes);
+ }
+}
diff --git a/test/test_pcm_all.h b/test/test_pcm_all.h
deleted file mode 100644
index 493619b55..000000000
--- a/test/test_pcm_all.h
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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_TEST_PCM_ALL_H
-#define MPD_TEST_PCM_ALL_H
-
-void
-test_pcm_dither_24(void);
-
-void
-test_pcm_dither_32(void);
-
-void
-test_pcm_pack_24(void);
-
-void
-test_pcm_unpack_24(void);
-
-void
-test_pcm_channels_16(void);
-
-void
-test_pcm_channels_32(void);
-
-void
-test_pcm_volume_8(void);
-
-void
-test_pcm_volume_16(void);
-
-void
-test_pcm_volume_24(void);
-
-void
-test_pcm_volume_32(void);
-
-void
-test_pcm_volume_float(void);
-
-#endif
diff --git a/test/test_pcm_all.hxx b/test/test_pcm_all.hxx
new file mode 100644
index 000000000..18202454b
--- /dev/null
+++ b/test/test_pcm_all.hxx
@@ -0,0 +1,80 @@
+/*
+ * 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_TEST_PCM_ALL_HXX
+#define MPD_TEST_PCM_ALL_HXX
+
+void
+test_pcm_dither_24();
+
+void
+test_pcm_dither_32();
+
+void
+test_pcm_pack_24();
+
+void
+test_pcm_unpack_24();
+
+void
+test_pcm_channels_16();
+
+void
+test_pcm_channels_32();
+
+void
+test_pcm_volume_8();
+
+void
+test_pcm_volume_16();
+
+void
+test_pcm_volume_24();
+
+void
+test_pcm_volume_32();
+
+void
+test_pcm_volume_float();
+
+void
+test_pcm_format_8_to_16();
+
+void
+test_pcm_format_16_to_24();
+
+void
+test_pcm_format_16_to_32();
+
+void
+test_pcm_format_float();
+
+void
+test_pcm_mix_8();
+
+void
+test_pcm_mix_16();
+
+void
+test_pcm_mix_24();
+
+void
+test_pcm_mix_32();
+
+#endif
diff --git a/test/test_pcm_channels.c b/test/test_pcm_channels.c
deleted file mode 100644
index fb3ec5c3e..000000000
--- a/test/test_pcm_channels.c
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * 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.
- */
-
-#include "config.h"
-#include "test_pcm_all.h"
-#include "pcm_channels.h"
-#include "pcm_buffer.h"
-
-#include <glib.h>
-
-void
-test_pcm_channels_16(void)
-{
- enum { N = 256 };
- int16_t src[N * 2];
-
- for (unsigned i = 0; i < G_N_ELEMENTS(src); ++i)
- src[i] = g_random_int();
-
- struct pcm_buffer buffer;
- pcm_buffer_init(&buffer);
-
- /* stereo to mono */
-
- size_t dest_size;
- const int16_t *dest =
- pcm_convert_channels_16(&buffer, 1, 2, src, sizeof(src),
- &dest_size);
- g_assert(dest != NULL);
- g_assert_cmpint(dest_size, ==, sizeof(src) / 2);
- for (unsigned i = 0; i < N; ++i)
- g_assert_cmpint(dest[i], ==,
- (src[i * 2] + src[i * 2 + 1]) / 2);
-
- /* mono to stereo */
-
- dest = pcm_convert_channels_16(&buffer, 2, 1, src, sizeof(src),
- &dest_size);
- g_assert(dest != NULL);
- g_assert_cmpint(dest_size, ==, sizeof(src) * 2);
- for (unsigned i = 0; i < N; ++i) {
- g_assert_cmpint(dest[i * 2], ==, src[i]);
- g_assert_cmpint(dest[i * 2 + 1], ==, src[i]);
- }
-
- pcm_buffer_deinit(&buffer);
-}
-
-void
-test_pcm_channels_32(void)
-{
- enum { N = 256 };
- int32_t src[N * 2];
-
- for (unsigned i = 0; i < G_N_ELEMENTS(src); ++i)
- src[i] = g_random_int();
-
- struct pcm_buffer buffer;
- pcm_buffer_init(&buffer);
-
- /* stereo to mono */
-
- size_t dest_size;
- const int32_t *dest =
- pcm_convert_channels_32(&buffer, 1, 2, src, sizeof(src),
- &dest_size);
- g_assert(dest != NULL);
- g_assert_cmpint(dest_size, ==, sizeof(src) / 2);
- for (unsigned i = 0; i < N; ++i)
- g_assert_cmpint(dest[i], ==,
- ((int64_t)src[i * 2] + (int64_t)src[i * 2 + 1]) / 2);
-
- /* mono to stereo */
-
- dest = pcm_convert_channels_32(&buffer, 2, 1, src, sizeof(src),
- &dest_size);
- g_assert(dest != NULL);
- g_assert_cmpint(dest_size, ==, sizeof(src) * 2);
- for (unsigned i = 0; i < N; ++i) {
- g_assert_cmpint(dest[i * 2], ==, src[i]);
- g_assert_cmpint(dest[i * 2 + 1], ==, src[i]);
- }
-
- pcm_buffer_deinit(&buffer);
-}
diff --git a/test/test_pcm_channels.cxx b/test/test_pcm_channels.cxx
new file mode 100644
index 000000000..6642ed3d4
--- /dev/null
+++ b/test/test_pcm_channels.cxx
@@ -0,0 +1,90 @@
+/*
+ * 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 "test_pcm_all.hxx"
+#include "test_pcm_util.hxx"
+#include "pcm/PcmChannels.hxx"
+#include "pcm/PcmBuffer.hxx"
+
+#include <glib.h>
+
+void
+test_pcm_channels_16()
+{
+ constexpr unsigned 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);
+ g_assert(dest != NULL);
+ g_assert_cmpint(dest_size, ==, sizeof(src) / 2);
+ for (unsigned i = 0; i < N; ++i)
+ g_assert_cmpint(dest[i], ==,
+ (src[i * 2] + src[i * 2 + 1]) / 2);
+
+ /* mono to stereo */
+
+ dest = pcm_convert_channels_16(buffer, 2, 1, src, sizeof(src),
+ &dest_size);
+ g_assert(dest != NULL);
+ g_assert_cmpint(dest_size, ==, sizeof(src) * 2);
+ for (unsigned i = 0; i < N; ++i) {
+ g_assert_cmpint(dest[i * 2], ==, src[i]);
+ g_assert_cmpint(dest[i * 2 + 1], ==, src[i]);
+ }
+}
+
+void
+test_pcm_channels_32()
+{
+ constexpr unsigned 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);
+ g_assert(dest != NULL);
+ g_assert_cmpint(dest_size, ==, sizeof(src) / 2);
+ for (unsigned i = 0; i < N; ++i)
+ g_assert_cmpint(dest[i], ==,
+ ((int64_t)src[i * 2] + (int64_t)src[i * 2 + 1]) / 2);
+
+ /* mono to stereo */
+
+ dest = pcm_convert_channels_32(buffer, 2, 1, src, sizeof(src),
+ &dest_size);
+ g_assert(dest != NULL);
+ g_assert_cmpint(dest_size, ==, sizeof(src) * 2);
+ for (unsigned i = 0; i < N; ++i) {
+ g_assert_cmpint(dest[i * 2], ==, src[i]);
+ g_assert_cmpint(dest[i * 2 + 1], ==, src[i]);
+ }
+}
diff --git a/test/test_pcm_dither.c b/test/test_pcm_dither.c
deleted file mode 100644
index 44d105207..000000000
--- a/test/test_pcm_dither.c
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * 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.
- */
-
-#include "test_pcm_all.h"
-#include "pcm_dither.h"
-
-#include <glib.h>
-
-/**
- * Generate a random 24 bit PCM sample.
- */
-static int32_t
-random24(void)
-{
- int32_t x = g_random_int() & 0xffffff;
- if (x & 0x800000)
- x |= 0xff000000;
- return x;
-}
-
-void
-test_pcm_dither_24(void)
-{
- struct pcm_dither dither;
-
- pcm_dither_24_init(&dither);
-
- enum { N = 256 };
- int32_t src[N];
- for (unsigned i = 0; i < N; ++i)
- src[i] = random24();
-
- int16_t dest[N];
-
- pcm_dither_24_to_16(&dither, dest, src, src + N);
-
- for (unsigned i = 0; i < N; ++i) {
- g_assert_cmpint(dest[i], >=, (src[i] >> 8) - 8);
- g_assert_cmpint(dest[i], <, (src[i] >> 8) + 8);
- }
-}
-
-void
-test_pcm_dither_32(void)
-{
- struct pcm_dither dither;
-
- pcm_dither_24_init(&dither);
-
- enum { N = 256 };
- int32_t src[N];
- for (unsigned i = 0; i < N; ++i)
- src[i] = g_random_int();
-
- int16_t dest[N];
-
- pcm_dither_32_to_16(&dither, dest, src, src + N);
-
- for (unsigned i = 0; i < N; ++i) {
- g_assert_cmpint(dest[i], >=, (src[i] >> 16) - 8);
- g_assert_cmpint(dest[i], <, (src[i] >> 16) + 8);
- }
-}
diff --git a/test/test_pcm_dither.cxx b/test/test_pcm_dither.cxx
new file mode 100644
index 000000000..5694c17f8
--- /dev/null
+++ b/test/test_pcm_dither.cxx
@@ -0,0 +1,56 @@
+/*
+ * 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 "test_pcm_all.hxx"
+#include "test_pcm_util.hxx"
+#include "pcm/PcmDither.hxx"
+
+#include <glib.h>
+
+void
+test_pcm_dither_24()
+{
+ constexpr unsigned N = 256;
+ const auto src = TestDataBuffer<int32_t, N>(GlibRandomInt24());
+
+ int16_t dest[N];
+ PcmDither dither;
+ dither.Dither24To16(dest, src.begin(), src.end());
+
+ for (unsigned i = 0; i < N; ++i) {
+ g_assert_cmpint(dest[i], >=, (src[i] >> 8) - 8);
+ g_assert_cmpint(dest[i], <, (src[i] >> 8) + 8);
+ }
+}
+
+void
+test_pcm_dither_32()
+{
+ constexpr unsigned N = 256;
+ const auto src = TestDataBuffer<int32_t, N>();
+
+ int16_t dest[N];
+ PcmDither dither;
+ dither.Dither32To16(dest, src.begin(), src.end());
+
+ for (unsigned i = 0; i < N; ++i) {
+ g_assert_cmpint(dest[i], >=, (src[i] >> 16) - 8);
+ g_assert_cmpint(dest[i], <, (src[i] >> 16) + 8);
+ }
+}
diff --git a/test/test_pcm_format.cxx b/test/test_pcm_format.cxx
new file mode 100644
index 000000000..65d744671
--- /dev/null
+++ b/test/test_pcm_format.cxx
@@ -0,0 +1,116 @@
+/*
+ * 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 "test_pcm_all.hxx"
+#include "test_pcm_util.hxx"
+#include "pcm/PcmFormat.hxx"
+#include "pcm/PcmDither.hxx"
+#include "pcm/PcmUtils.hxx"
+#include "pcm/PcmBuffer.hxx"
+#include "AudioFormat.hxx"
+
+#include <glib.h>
+
+void
+test_pcm_format_8_to_16()
+{
+ constexpr unsigned 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);
+ g_assert_cmpint(d_end - d, ==, N);
+
+ for (size_t i = 0; i < N; ++i)
+ g_assert_cmpint(src[i], ==, d[i] >> 8);
+}
+
+void
+test_pcm_format_16_to_24()
+{
+ constexpr unsigned 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);
+ g_assert_cmpint(d_end - d, ==, N);
+
+ for (size_t i = 0; i < N; ++i)
+ g_assert_cmpint(src[i], ==, d[i] >> 8);
+}
+
+void
+test_pcm_format_16_to_32()
+{
+ constexpr unsigned 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);
+ g_assert_cmpint(d_end - d, ==, N);
+
+ for (size_t i = 0; i < N; ++i)
+ g_assert_cmpint(src[i], ==, d[i] >> 16);
+}
+
+void
+test_pcm_format_float()
+{
+ constexpr unsigned 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);
+ g_assert_cmpint(f_end - f, ==, N);
+
+ for (auto i = f; i != f_end; ++i) {
+ g_assert(*i >= -1.);
+ g_assert(*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);
+ g_assert_cmpint(d_end - d, ==, N);
+
+ for (size_t i = 0; i < N; ++i)
+ g_assert_cmpint(src[i], ==, d[i]);
+}
diff --git a/test/test_pcm_main.c b/test/test_pcm_main.c
deleted file mode 100644
index a483baaab..000000000
--- a/test/test_pcm_main.c
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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.
- */
-
-#include "test_pcm_all.h"
-
-#include <glib.h>
-
-int
-main(int argc, char **argv)
-{
- g_test_init (&argc, &argv, NULL);
- g_test_add_func("/pcm/dither/24", test_pcm_dither_24);
- g_test_add_func("/pcm/dither/32", test_pcm_dither_32);
- g_test_add_func("/pcm/pack/pack24", test_pcm_pack_24);
- g_test_add_func("/pcm/pack/unpack24", test_pcm_unpack_24);
- g_test_add_func("/pcm/channels/16", test_pcm_channels_16);
- g_test_add_func("/pcm/channels/32", test_pcm_channels_32);
-
- g_test_add_func("/pcm/volume/8", test_pcm_volume_8);
- g_test_add_func("/pcm/volume/16", test_pcm_volume_16);
- g_test_add_func("/pcm/volume/24", test_pcm_volume_24);
- g_test_add_func("/pcm/volume/32", test_pcm_volume_32);
- g_test_add_func("/pcm/volume/float", test_pcm_volume_float);
-
- g_test_run();
-}
diff --git a/test/test_pcm_main.cxx b/test/test_pcm_main.cxx
new file mode 100644
index 000000000..a221b26af
--- /dev/null
+++ b/test/test_pcm_main.cxx
@@ -0,0 +1,52 @@
+/*
+ * 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 "test_pcm_all.hxx"
+
+#include <glib.h>
+
+int
+main(int argc, char **argv)
+{
+ g_test_init (&argc, &argv, NULL);
+ g_test_add_func("/pcm/dither/24", test_pcm_dither_24);
+ g_test_add_func("/pcm/dither/32", test_pcm_dither_32);
+ g_test_add_func("/pcm/pack/pack24", test_pcm_pack_24);
+ g_test_add_func("/pcm/pack/unpack24", test_pcm_unpack_24);
+ g_test_add_func("/pcm/channels/16", test_pcm_channels_16);
+ g_test_add_func("/pcm/channels/32", test_pcm_channels_32);
+
+ g_test_add_func("/pcm/volume/8", test_pcm_volume_8);
+ g_test_add_func("/pcm/volume/16", test_pcm_volume_16);
+ g_test_add_func("/pcm/volume/24", test_pcm_volume_24);
+ g_test_add_func("/pcm/volume/32", test_pcm_volume_32);
+ g_test_add_func("/pcm/volume/float", test_pcm_volume_float);
+
+ g_test_add_func("/pcm/format/8_to_16", test_pcm_format_8_to_16);
+ g_test_add_func("/pcm/format/16_to_24", test_pcm_format_16_to_24);
+ g_test_add_func("/pcm/format/16_to_32", test_pcm_format_16_to_32);
+ g_test_add_func("/pcm/format/float", test_pcm_format_float);
+
+ g_test_add_func("/pcm/mix/8", test_pcm_mix_8);
+ g_test_add_func("/pcm/mix/16", test_pcm_mix_16);
+ g_test_add_func("/pcm/mix/24", test_pcm_mix_24);
+ g_test_add_func("/pcm/mix/32", test_pcm_mix_32);
+
+ g_test_run();
+}
diff --git a/test/test_pcm_mix.cxx b/test/test_pcm_mix.cxx
new file mode 100644
index 000000000..b0e89639c
--- /dev/null
+++ b/test/test_pcm_mix.cxx
@@ -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.
+ */
+
+#include "config.h"
+#include "test_pcm_all.hxx"
+#include "test_pcm_util.hxx"
+#include "pcm/PcmMix.hxx"
+
+#include <glib.h>
+
+template<typename T, SampleFormat format, typename G=GlibRandomInt<T>>
+void
+TestPcmMix(G g=G())
+{
+ constexpr unsigned N = 256;
+ const auto src1 = TestDataBuffer<T, N>(g);
+ const auto src2 = TestDataBuffer<T, N>(g);
+
+ /* portion1=1.0: result must be equal to src1 */
+ auto result = src1;
+ bool success = pcm_mix(result.begin(), src2.begin(), sizeof(result),
+ format, 1.0);
+ g_assert(success);
+ AssertEqualWithTolerance(result, src1, 1);
+
+ /* portion1=0.0: result must be equal to src2 */
+ result = src1;
+ success = pcm_mix(result.begin(), src2.begin(), sizeof(result),
+ format, 0.0);
+ g_assert(success);
+ AssertEqualWithTolerance(result, src2, 1);
+
+ /* portion1=0.5 */
+ result = src1;
+ success = pcm_mix(result.begin(), src2.begin(), sizeof(result),
+ format, 0.5);
+ g_assert(success);
+
+ auto expected = src1;
+ for (unsigned i = 0; i < N; ++i)
+ expected[i] = (int64_t(src1[i]) + int64_t(src2[i])) / 2;
+
+ AssertEqualWithTolerance(result, expected, 1);
+}
+
+void
+test_pcm_mix_8()
+{
+ TestPcmMix<int8_t, SampleFormat::S8>();
+}
+
+void
+test_pcm_mix_16()
+{
+ TestPcmMix<int16_t, SampleFormat::S16>();
+}
+
+void
+test_pcm_mix_24()
+{
+ TestPcmMix<int32_t, SampleFormat::S24_P32>(GlibRandomInt24());
+}
+
+void
+test_pcm_mix_32()
+{
+ TestPcmMix<int32_t, SampleFormat::S32>();
+}
diff --git a/test/test_pcm_pack.c b/test/test_pcm_pack.c
deleted file mode 100644
index 5127536fb..000000000
--- a/test/test_pcm_pack.c
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * 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.
- */
-
-#include "test_pcm_all.h"
-#include "pcm_pack.h"
-
-#include <glib.h>
-
-/**
- * Generate a random 24 bit PCM sample.
- */
-static int32_t
-random24(void)
-{
- int32_t x = g_random_int() & 0xffffff;
- if (x & 0x800000)
- x |= 0xff000000;
- return x;
-}
-
-void
-test_pcm_pack_24(void)
-{
- enum { N = 256 };
- int32_t src[N * 3];
- for (unsigned i = 0; i < N; ++i)
- src[i] = random24();
-
- uint8_t dest[N * 3];
-
- pcm_pack_24(dest, src, src + N);
-
- for (unsigned i = 0; i < N; ++i) {
- int32_t d;
- if (G_BYTE_ORDER == G_BIG_ENDIAN)
- d = (dest[i * 3] << 16) | (dest[i * 3 + 1] << 8)
- | dest[i * 3 + 2];
- else
- d = (dest[i * 3 + 2] << 16) | (dest[i * 3 + 1] << 8)
- | dest[i * 3];
- if (d & 0x800000)
- d |= 0xff000000;
-
- g_assert_cmpint(d, ==, src[i]);
- }
-}
-
-void
-test_pcm_unpack_24(void)
-{
- enum { N = 256 };
- uint8_t src[N * 3];
- for (unsigned i = 0; i < G_N_ELEMENTS(src); ++i)
- src[i] = g_random_int_range(0, 256);
-
- int32_t dest[N];
-
- pcm_unpack_24(dest, src, src + G_N_ELEMENTS(src));
-
- for (unsigned i = 0; i < N; ++i) {
- int32_t s;
- if (G_BYTE_ORDER == G_BIG_ENDIAN)
- s = (src[i * 3] << 16) | (src[i * 3 + 1] << 8)
- | src[i * 3 + 2];
- else
- s = (src[i * 3 + 2] << 16) | (src[i * 3 + 1] << 8)
- | src[i * 3];
- if (s & 0x800000)
- s |= 0xff000000;
-
- g_assert_cmpint(s, ==, dest[i]);
- }
-}
diff --git a/test/test_pcm_pack.cxx b/test/test_pcm_pack.cxx
new file mode 100644
index 000000000..313948838
--- /dev/null
+++ b/test/test_pcm_pack.cxx
@@ -0,0 +1,75 @@
+/*
+ * 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 "test_pcm_all.hxx"
+#include "test_pcm_util.hxx"
+
+extern "C" {
+#include "pcm/pcm_pack.h"
+}
+
+#include <glib.h>
+
+void
+test_pcm_pack_24()
+{
+ constexpr unsigned N = 256;
+ const auto src = TestDataBuffer<int32_t, N>(GlibRandomInt24());
+
+ uint8_t dest[N * 3];
+ pcm_pack_24(dest, src.begin(), src.end());
+
+ for (unsigned i = 0; i < N; ++i) {
+ int32_t d;
+ if (G_BYTE_ORDER == G_BIG_ENDIAN)
+ d = (dest[i * 3] << 16) | (dest[i * 3 + 1] << 8)
+ | dest[i * 3 + 2];
+ else
+ d = (dest[i * 3 + 2] << 16) | (dest[i * 3 + 1] << 8)
+ | dest[i * 3];
+ if (d & 0x800000)
+ d |= 0xff000000;
+
+ g_assert_cmpint(d, ==, src[i]);
+ }
+}
+
+void
+test_pcm_unpack_24()
+{
+ constexpr unsigned N = 256;
+ const auto src = TestDataBuffer<uint8_t, N * 3>();
+
+ int32_t dest[N];
+ pcm_unpack_24(dest, src.begin(), src.end());
+
+ for (unsigned i = 0; i < N; ++i) {
+ int32_t s;
+ if (G_BYTE_ORDER == G_BIG_ENDIAN)
+ s = (src[i * 3] << 16) | (src[i * 3 + 1] << 8)
+ | src[i * 3 + 2];
+ else
+ s = (src[i * 3 + 2] << 16) | (src[i * 3 + 1] << 8)
+ | src[i * 3];
+ if (s & 0x800000)
+ s |= 0xff000000;
+
+ g_assert_cmpint(s, ==, dest[i]);
+ }
+}
diff --git a/test/test_pcm_util.hxx b/test/test_pcm_util.hxx
new file mode 100644
index 000000000..84ba074fd
--- /dev/null
+++ b/test/test_pcm_util.hxx
@@ -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 <glib.h>
+
+#include <array>
+
+#include <stddef.h>
+#include <stdint.h>
+
+template<typename T>
+struct GlibRandomInt {
+ T operator()() const {
+ return T(g_random_int());
+ }
+};
+
+struct GlibRandomInt24 : GlibRandomInt<int32_t> {
+ int32_t operator()() const {
+ auto t = GlibRandomInt::operator()();
+ t &= 0xffffff;
+ if (t & 0x800000)
+ t |= 0xff000000;
+ return t;
+ }
+};
+
+struct GlibRandomFloat {
+ float operator()() const {
+ return g_random_double_range(-1.0, 1.0);
+ }
+};
+
+template<typename T, size_t N>
+class TestDataBuffer : std::array<T, N> {
+public:
+ using typename std::array<T, N>::const_pointer;
+ using std::array<T, N>::size;
+ using std::array<T, N>::begin;
+ using std::array<T, N>::end;
+ using std::array<T, N>::operator[];
+
+ template<typename G=GlibRandomInt<T>>
+ TestDataBuffer(G g=G()):std::array<T, N>() {
+ for (auto &i : *this)
+ i = g();
+
+ }
+
+ operator typename std::array<T, N>::const_pointer() const {
+ return begin();
+ }
+};
+
+template<typename T>
+bool
+AssertEqualWithTolerance(const T &a, const T &b, unsigned tolerance)
+{
+ g_assert_cmpint(a.size(), ==, b.size());
+
+ for (unsigned i = 0; i < a.size(); ++i) {
+ int64_t x = a[i], y = b[i];
+
+ g_assert_cmpint(x, >=, y - int64_t(tolerance));
+ g_assert_cmpint(x, <=, y + int64_t(tolerance));
+ }
+
+ return true;
+}
diff --git a/test/test_pcm_volume.c b/test/test_pcm_volume.c
deleted file mode 100644
index 713645cf1..000000000
--- a/test/test_pcm_volume.c
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * 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 "test_pcm_all.h"
-#include "pcm_volume.h"
-
-#include <glib.h>
-
-#include <string.h>
-
-void
-test_pcm_volume_8(void)
-{
- enum { N = 256 };
- static const int8_t zero[N];
- int8_t src[N];
- for (unsigned i = 0; i < N; ++i)
- src[i] = g_random_int();
-
- int8_t dest[N];
-
- memcpy(dest, src, sizeof(src));
- g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S8,
- 0), ==, true);
- g_assert_cmpint(memcmp(dest, zero, sizeof(zero)), ==, 0);
-
- memcpy(dest, src, sizeof(src));
- g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S8,
- PCM_VOLUME_1), ==, true);
- g_assert_cmpint(memcmp(dest, src, sizeof(src)), ==, 0);
-
- memcpy(dest, src, sizeof(src));
- g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S8,
- PCM_VOLUME_1 / 2), ==, true);
-
- for (unsigned i = 0; i < N; ++i) {
- g_assert_cmpint(dest[i], >=, (src[i] - 1) / 2);
- g_assert_cmpint(dest[i], <=, src[i] / 2 + 1);
- }
-}
-
-void
-test_pcm_volume_16(void)
-{
- enum { N = 256 };
- static const int16_t zero[N];
- int16_t src[N];
- for (unsigned i = 0; i < N; ++i)
- src[i] = g_random_int();
-
- int16_t dest[N];
-
- memcpy(dest, src, sizeof(src));
- g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S16,
- 0), ==, true);
- g_assert_cmpint(memcmp(dest, zero, sizeof(zero)), ==, 0);
-
- memcpy(dest, src, sizeof(src));
- g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S16,
- PCM_VOLUME_1), ==, true);
- g_assert_cmpint(memcmp(dest, src, sizeof(src)), ==, 0);
-
- memcpy(dest, src, sizeof(src));
- g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S16,
- PCM_VOLUME_1 / 2), ==, true);
-
- for (unsigned i = 0; i < N; ++i) {
- g_assert_cmpint(dest[i], >=, (src[i] - 1) / 2);
- g_assert_cmpint(dest[i], <=, src[i] / 2 + 1);
- }
-}
-
-/**
- * Generate a random 24 bit PCM sample.
- */
-static int32_t
-random24(void)
-{
- int32_t x = g_random_int() & 0xffffff;
- if (x & 0x800000)
- x |= 0xff000000;
- return x;
-}
-
-void
-test_pcm_volume_24(void)
-{
- enum { N = 256 };
- static const int32_t zero[N];
- int32_t src[N];
- for (unsigned i = 0; i < N; ++i)
- src[i] = random24();
-
- int32_t dest[N];
-
- memcpy(dest, src, sizeof(src));
- g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S24_P32,
- 0), ==, true);
- g_assert_cmpint(memcmp(dest, zero, sizeof(zero)), ==, 0);
-
- memcpy(dest, src, sizeof(src));
- g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S24_P32,
- PCM_VOLUME_1), ==, true);
- g_assert_cmpint(memcmp(dest, src, sizeof(src)), ==, 0);
-
- memcpy(dest, src, sizeof(src));
- g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S24_P32,
- PCM_VOLUME_1 / 2), ==, true);
-
- for (unsigned i = 0; i < N; ++i) {
- g_assert_cmpint(dest[i], >=, (src[i] - 1) / 2);
- g_assert_cmpint(dest[i], <=, src[i] / 2 + 1);
- }
-}
-
-void
-test_pcm_volume_32(void)
-{
- enum { N = 256 };
- static const int32_t zero[N];
- int32_t src[N];
- for (unsigned i = 0; i < N; ++i)
- src[i] = g_random_int();
-
- int32_t dest[N];
-
- memcpy(dest, src, sizeof(src));
- g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S32,
- 0), ==, true);
- g_assert_cmpint(memcmp(dest, zero, sizeof(zero)), ==, 0);
-
- memcpy(dest, src, sizeof(src));
- g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S32,
- PCM_VOLUME_1), ==, true);
- g_assert_cmpint(memcmp(dest, src, sizeof(src)), ==, 0);
-
- memcpy(dest, src, sizeof(src));
- g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S32,
- PCM_VOLUME_1 / 2), ==, true);
-
- for (unsigned i = 0; i < N; ++i) {
- g_assert_cmpint(dest[i], >=, (src[i] - 1) / 2);
- g_assert_cmpint(dest[i], <=, src[i] / 2 + 1);
- }
-}
-
-void
-test_pcm_volume_float(void)
-{
- enum { N = 256 };
- static const float zero[N];
- float src[N];
- for (unsigned i = 0; i < N; ++i)
- src[i] = g_random_double_range(-1.0, 1.0);
-
- float dest[N];
-
- memcpy(dest, src, sizeof(src));
- g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_FLOAT,
- 0), ==, true);
- g_assert_cmpint(memcmp(dest, zero, sizeof(zero)), ==, 0);
-
- memcpy(dest, src, sizeof(src));
- g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_FLOAT,
- PCM_VOLUME_1), ==, true);
- g_assert_cmpint(memcmp(dest, src, sizeof(src)), ==, 0);
-
- memcpy(dest, src, sizeof(src));
- g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_FLOAT,
- PCM_VOLUME_1 / 2), ==, true);
-
- for (unsigned i = 0; i < N; ++i)
- g_assert_cmpfloat(dest[i], ==, src[i] / 2);
-}
diff --git a/test/test_pcm_volume.cxx b/test/test_pcm_volume.cxx
new file mode 100644
index 000000000..d5aa3782e
--- /dev/null
+++ b/test/test_pcm_volume.cxx
@@ -0,0 +1,171 @@
+/*
+ * 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 "test_pcm_all.hxx"
+#include "pcm/PcmVolume.hxx"
+#include "test_pcm_util.hxx"
+
+#include <glib.h>
+
+#include <algorithm>
+
+#include <string.h>
+
+void
+test_pcm_volume_8()
+{
+ constexpr unsigned N = 256;
+ static int8_t zero[N];
+ const auto src = TestDataBuffer<int8_t, N>();
+
+ int8_t dest[N];
+
+ std::copy(src.begin(), src.end(), dest);
+ g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::S8,
+ 0), ==, true);
+ g_assert_cmpint(memcmp(dest, zero, sizeof(zero)), ==, 0);
+
+ std::copy(src.begin(), src.end(), dest);
+ g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::S8,
+ PCM_VOLUME_1), ==, true);
+ g_assert_cmpint(memcmp(dest, src, sizeof(src)), ==, 0);
+
+ std::copy(src.begin(), src.end(), dest);
+ g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::S8,
+ PCM_VOLUME_1 / 2), ==, true);
+
+ for (unsigned i = 0; i < N; ++i) {
+ g_assert_cmpint(dest[i], >=, (src[i] - 1) / 2);
+ g_assert_cmpint(dest[i], <=, src[i] / 2 + 1);
+ }
+}
+
+void
+test_pcm_volume_16()
+{
+ 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);
+ g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::S16,
+ 0), ==, true);
+ g_assert_cmpint(memcmp(dest, zero, sizeof(zero)), ==, 0);
+
+ std::copy(src.begin(), src.end(), dest);
+ g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::S16,
+ PCM_VOLUME_1), ==, true);
+ g_assert_cmpint(memcmp(dest, src, sizeof(src)), ==, 0);
+
+ std::copy(src.begin(), src.end(), dest);
+ g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::S16,
+ PCM_VOLUME_1 / 2), ==, true);
+
+ for (unsigned i = 0; i < N; ++i) {
+ g_assert_cmpint(dest[i], >=, (src[i] - 1) / 2);
+ g_assert_cmpint(dest[i], <=, src[i] / 2 + 1);
+ }
+}
+
+void
+test_pcm_volume_24()
+{
+ constexpr unsigned N = 256;
+ static int32_t zero[N];
+ const auto src = TestDataBuffer<int32_t, N>(GlibRandomInt24());
+
+ int32_t dest[N];
+
+ std::copy(src.begin(), src.end(), dest);
+ g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::S24_P32,
+ 0), ==, true);
+ g_assert_cmpint(memcmp(dest, zero, sizeof(zero)), ==, 0);
+
+ std::copy(src.begin(), src.end(), dest);
+ g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::S24_P32,
+ PCM_VOLUME_1), ==, true);
+ g_assert_cmpint(memcmp(dest, src, sizeof(src)), ==, 0);
+
+ std::copy(src.begin(), src.end(), dest);
+ g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::S24_P32,
+ PCM_VOLUME_1 / 2), ==, true);
+
+ for (unsigned i = 0; i < N; ++i) {
+ g_assert_cmpint(dest[i], >=, (src[i] - 1) / 2);
+ g_assert_cmpint(dest[i], <=, src[i] / 2 + 1);
+ }
+}
+
+void
+test_pcm_volume_32()
+{
+ 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);
+ g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::S32,
+ 0), ==, true);
+ g_assert_cmpint(memcmp(dest, zero, sizeof(zero)), ==, 0);
+
+ std::copy(src.begin(), src.end(), dest);
+ g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::S32,
+ PCM_VOLUME_1), ==, true);
+ g_assert_cmpint(memcmp(dest, src, sizeof(src)), ==, 0);
+
+ std::copy(src.begin(), src.end(), dest);
+ g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::S32,
+ PCM_VOLUME_1 / 2), ==, true);
+
+ for (unsigned i = 0; i < N; ++i) {
+ g_assert_cmpint(dest[i], >=, (src[i] - 1) / 2);
+ g_assert_cmpint(dest[i], <=, src[i] / 2 + 1);
+ }
+}
+
+void
+test_pcm_volume_float()
+{
+ constexpr unsigned N = 256;
+ static float zero[N];
+ const auto src = TestDataBuffer<float, N>(GlibRandomFloat());
+
+ float dest[N];
+
+ std::copy(src.begin(), src.end(), dest);
+ g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::FLOAT,
+ 0), ==, true);
+ g_assert_cmpint(memcmp(dest, zero, sizeof(zero)), ==, 0);
+
+ std::copy(src.begin(), src.end(), dest);
+ g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::FLOAT,
+ PCM_VOLUME_1), ==, true);
+ g_assert_cmpint(memcmp(dest, src, sizeof(src)), ==, 0);
+
+ std::copy(src.begin(), src.end(), dest);
+ g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::FLOAT,
+ PCM_VOLUME_1 / 2), ==, true);
+
+ for (unsigned i = 0; i < N; ++i)
+ g_assert_cmpfloat(dest[i], ==, src[i] / 2);
+}
diff --git a/test/test_queue_priority.c b/test/test_queue_priority.c
deleted file mode 100644
index 5543edbba..000000000
--- a/test/test_queue_priority.c
+++ /dev/null
@@ -1,175 +0,0 @@
-#include "queue.h"
-#include "song.h"
-
-void
-song_free(G_GNUC_UNUSED struct song *song)
-{
-}
-
-G_GNUC_UNUSED
-static void
-dump_order(const struct queue *queue)
-{
- g_printerr("queue length=%u, order:\n", queue_length(queue));
- for (unsigned i = 0; i < queue_length(queue); ++i)
- g_printerr(" [%u] -> %u (prio=%u)\n", i, queue->order[i],
- queue->items[queue->order[i]].priority);
-}
-
-static void
-check_descending_priority(G_GNUC_UNUSED const struct queue *queue,
- unsigned start_order)
-{
- assert(start_order < queue_length(queue));
-
- uint8_t last_priority = 0xff;
- for (unsigned order = start_order; order < queue_length(queue); ++order) {
- unsigned position = queue_order_to_position(queue, order);
- uint8_t priority = queue->items[position].priority;
- assert(priority <= last_priority);
- (void)last_priority;
- last_priority = priority;
- }
-}
-
-int
-main(G_GNUC_UNUSED int argc, G_GNUC_UNUSED char **argv)
-{
- struct song songs[16];
-
- struct queue queue;
- queue_init(&queue, 32);
-
- for (unsigned i = 0; i < G_N_ELEMENTS(songs); ++i)
- queue_append(&queue, &songs[i], 0);
-
- assert(queue_length(&queue) == G_N_ELEMENTS(songs));
-
- /* priority=10 for 4 items */
-
- queue_set_priority_range(&queue, 4, 8, 10, -1);
-
- queue.random = true;
- queue_shuffle_order(&queue);
- check_descending_priority(&queue, 0);
-
- for (unsigned i = 0; i < 4; ++i) {
- assert(queue_position_to_order(&queue, i) >= 4);
- }
-
- for (unsigned i = 4; i < 8; ++i) {
- assert(queue_position_to_order(&queue, i) < 4);
- }
-
- for (unsigned i = 8; i < G_N_ELEMENTS(songs); ++i) {
- assert(queue_position_to_order(&queue, i) >= 4);
- }
-
- /* priority=50 one more item */
-
- queue_set_priority_range(&queue, 15, 16, 50, -1);
- check_descending_priority(&queue, 0);
-
- assert(queue_position_to_order(&queue, 15) == 0);
-
- for (unsigned i = 0; i < 4; ++i) {
- assert(queue_position_to_order(&queue, i) >= 4);
- }
-
- for (unsigned i = 4; i < 8; ++i) {
- assert(queue_position_to_order(&queue, i) >= 1 &&
- queue_position_to_order(&queue, i) < 5);
- }
-
- for (unsigned i = 8; i < 15; ++i) {
- assert(queue_position_to_order(&queue, i) >= 5);
- }
-
- /* priority=20 for one of the 4 priority=10 items */
-
- queue_set_priority_range(&queue, 3, 4, 20, -1);
- check_descending_priority(&queue, 0);
-
- assert(queue_position_to_order(&queue, 3) == 1);
- assert(queue_position_to_order(&queue, 15) == 0);
-
- for (unsigned i = 0; i < 3; ++i) {
- assert(queue_position_to_order(&queue, i) >= 5);
- }
-
- for (unsigned i = 4; i < 8; ++i) {
- assert(queue_position_to_order(&queue, i) >= 2 &&
- queue_position_to_order(&queue, i) < 6);
- }
-
- for (unsigned i = 8; i < 15; ++i) {
- assert(queue_position_to_order(&queue, i) >= 6);
- }
-
- /* priority=20 for another one of the 4 priority=10 items;
- pass "after_order" (with priority=10) and see if it's moved
- after that one */
-
- unsigned current_order = 4;
- unsigned current_position =
- queue_order_to_position(&queue, current_order);
-
- unsigned a_order = 3;
- unsigned a_position = queue_order_to_position(&queue, a_order);
- assert(queue.items[a_position].priority == 10);
- queue_set_priority(&queue, a_position, 20, current_order);
-
- current_order = queue_position_to_order(&queue, current_position);
- assert(current_order == 3);
-
- a_order = queue_position_to_order(&queue, a_position);
- assert(a_order == 4);
-
- check_descending_priority(&queue, current_order + 1);
-
- /* priority=70 for one of the last items; must be inserted
- right after the current song, before the priority=20 one we
- just created */
-
- unsigned b_order = 10;
- unsigned b_position = queue_order_to_position(&queue, b_order);
- assert(queue.items[b_position].priority == 0);
- queue_set_priority(&queue, b_position, 70, current_order);
-
- current_order = queue_position_to_order(&queue, current_position);
- assert(current_order == 3);
-
- b_order = queue_position_to_order(&queue, b_position);
- assert(b_order == 4);
-
- check_descending_priority(&queue, current_order + 1);
-
- /* priority=60 for the old prio50 item; must not be moved,
- because it's before the current song, and it's status
- hasn't changed (it was already higher before) */
-
- unsigned c_order = 0;
- unsigned c_position = queue_order_to_position(&queue, c_order);
- assert(queue.items[c_position].priority == 50);
- queue_set_priority(&queue, c_position, 60, current_order);
-
- current_order = queue_position_to_order(&queue, current_position);
- assert(current_order == 3);
-
- c_order = queue_position_to_order(&queue, c_position);
- assert(c_order == 0);
-
- /* move the prio=20 item back */
-
- a_order = queue_position_to_order(&queue, a_position);
- assert(a_order == 5);
- assert(queue.items[a_position].priority == 20);
- queue_set_priority(&queue, a_position, 5, current_order);
-
-
- current_order = queue_position_to_order(&queue, current_position);
- assert(current_order == 3);
-
- a_order = queue_position_to_order(&queue, a_position);
- assert(a_order == 6);
-}
diff --git a/test/test_queue_priority.cxx b/test/test_queue_priority.cxx
new file mode 100644
index 000000000..2e544253d
--- /dev/null
+++ b/test/test_queue_priority.cxx
@@ -0,0 +1,188 @@
+#include "config.h"
+#include "Queue.hxx"
+#include "Song.hxx"
+#include "Directory.hxx"
+
+#include <glib.h>
+
+Directory detached_root;
+
+Directory::Directory() {}
+Directory::~Directory() {}
+
+Song *
+Song::DupDetached() const
+{
+ return const_cast<Song *>(this);
+}
+
+void
+Song::Free()
+{
+}
+
+gcc_unused
+static void
+dump_order(const struct queue *queue)
+{
+ g_printerr("queue length=%u, order:\n", queue->GetLength());
+ for (unsigned i = 0; i < queue->GetLength(); ++i)
+ g_printerr(" [%u] -> %u (prio=%u)\n", i, queue->order[i],
+ queue->items[queue->order[i]].priority);
+}
+
+static void
+check_descending_priority(const struct queue *queue,
+ unsigned start_order)
+{
+ assert(start_order < queue->GetLength());
+
+ uint8_t last_priority = 0xff;
+ for (unsigned order = start_order; order < queue->GetLength(); ++order) {
+ unsigned position = queue->OrderToPosition(order);
+ uint8_t priority = queue->items[position].priority;
+ assert(priority <= last_priority);
+ (void)last_priority;
+ last_priority = priority;
+ }
+}
+
+int
+main(gcc_unused int argc, gcc_unused char **argv)
+{
+ static Song songs[16];
+
+ struct queue queue(32);
+
+ for (unsigned i = 0; i < G_N_ELEMENTS(songs); ++i)
+ queue.Append(&songs[i], 0);
+
+ assert(queue.GetLength() == G_N_ELEMENTS(songs));
+
+ /* priority=10 for 4 items */
+
+ queue.SetPriorityRange(4, 8, 10, -1);
+
+ queue.random = true;
+ queue.ShuffleOrder();
+ check_descending_priority(&queue, 0);
+
+ for (unsigned i = 0; i < 4; ++i) {
+ assert(queue.PositionToOrder(i) >= 4);
+ }
+
+ for (unsigned i = 4; i < 8; ++i) {
+ assert(queue.PositionToOrder(i) < 4);
+ }
+
+ for (unsigned i = 8; i < G_N_ELEMENTS(songs); ++i) {
+ assert(queue.PositionToOrder(i) >= 4);
+ }
+
+ /* priority=50 one more item */
+
+ queue.SetPriorityRange(15, 16, 50, -1);
+ check_descending_priority(&queue, 0);
+
+ assert(queue.PositionToOrder(15) == 0);
+
+ for (unsigned i = 0; i < 4; ++i) {
+ assert(queue.PositionToOrder(i) >= 4);
+ }
+
+ for (unsigned i = 4; i < 8; ++i) {
+ assert(queue.PositionToOrder(i) >= 1 &&
+ queue.PositionToOrder(i) < 5);
+ }
+
+ for (unsigned i = 8; i < 15; ++i) {
+ assert(queue.PositionToOrder(i) >= 5);
+ }
+
+ /* priority=20 for one of the 4 priority=10 items */
+
+ queue.SetPriorityRange(3, 4, 20, -1);
+ check_descending_priority(&queue, 0);
+
+ assert(queue.PositionToOrder(3) == 1);
+ assert(queue.PositionToOrder(15) == 0);
+
+ for (unsigned i = 0; i < 3; ++i) {
+ assert(queue.PositionToOrder(i) >= 5);
+ }
+
+ for (unsigned i = 4; i < 8; ++i) {
+ assert(queue.PositionToOrder(i) >= 2 &&
+ queue.PositionToOrder(i) < 6);
+ }
+
+ for (unsigned i = 8; i < 15; ++i) {
+ assert(queue.PositionToOrder(i) >= 6);
+ }
+
+ /* priority=20 for another one of the 4 priority=10 items;
+ pass "after_order" (with priority=10) and see if it's moved
+ after that one */
+
+ unsigned current_order = 4;
+ unsigned current_position =
+ queue.OrderToPosition(current_order);
+
+ unsigned a_order = 3;
+ unsigned a_position = queue.OrderToPosition(a_order);
+ assert(queue.items[a_position].priority == 10);
+ queue.SetPriority(a_position, 20, current_order);
+
+ current_order = queue.PositionToOrder(current_position);
+ assert(current_order == 3);
+
+ a_order = queue.PositionToOrder(a_position);
+ assert(a_order == 4);
+
+ check_descending_priority(&queue, current_order + 1);
+
+ /* priority=70 for one of the last items; must be inserted
+ right after the current song, before the priority=20 one we
+ just created */
+
+ unsigned b_order = 10;
+ unsigned b_position = queue.OrderToPosition(b_order);
+ assert(queue.items[b_position].priority == 0);
+ queue.SetPriority(b_position, 70, current_order);
+
+ current_order = queue.PositionToOrder(current_position);
+ assert(current_order == 3);
+
+ b_order = queue.PositionToOrder(b_position);
+ assert(b_order == 4);
+
+ check_descending_priority(&queue, current_order + 1);
+
+ /* priority=60 for the old prio50 item; must not be moved,
+ because it's before the current song, and it's status
+ hasn't changed (it was already higher before) */
+
+ unsigned c_order = 0;
+ unsigned c_position = queue.OrderToPosition(c_order);
+ assert(queue.items[c_position].priority == 50);
+ queue.SetPriority(c_position, 60, current_order);
+
+ current_order = queue.PositionToOrder(current_position);
+ assert(current_order == 3);
+
+ c_order = queue.PositionToOrder(c_position);
+ assert(c_order == 0);
+
+ /* move the prio=20 item back */
+
+ a_order = queue.PositionToOrder(a_position);
+ assert(a_order == 5);
+ assert(queue.items[a_position].priority == 20);
+ queue.SetPriority(a_position, 5, current_order);
+
+ current_order = queue.PositionToOrder(current_position);
+ assert(current_order == 3);
+
+ a_order = queue.PositionToOrder(a_position);
+ assert(a_order == 6);
+}
diff --git a/test/test_vorbis_encoder.c b/test/test_vorbis_encoder.c
deleted file mode 100644
index 619399159..000000000
--- a/test/test_vorbis_encoder.c
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * 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 "encoder_list.h"
-#include "encoder_plugin.h"
-#include "audio_format.h"
-#include "conf.h"
-#include "stdbin.h"
-#include "tag.h"
-
-#include <glib.h>
-
-#include <stddef.h>
-#include <unistd.h>
-
-static uint8_t zero[256];
-
-static void
-encoder_to_stdout(struct encoder *encoder)
-{
- size_t length;
- static char buffer[32768];
-
- while ((length = encoder_read(encoder, buffer, sizeof(buffer))) > 0) {
- G_GNUC_UNUSED ssize_t ignored = write(1, buffer, length);
- }
-}
-
-int
-main(G_GNUC_UNUSED int argc, G_GNUC_UNUSED char **argv)
-{
- G_GNUC_UNUSED bool success;
-
- /* create the encoder */
-
- const struct encoder_plugin *plugin = encoder_plugin_get("vorbis");
- assert(plugin != NULL);
-
- struct config_param *param = config_new_param(NULL, -1);
- config_add_block_param(param, "quality", "5.0", -1);
-
- struct encoder *encoder = encoder_init(plugin, param, NULL);
- assert(encoder != NULL);
-
- /* open the encoder */
-
- struct audio_format audio_format;
-
- audio_format_init(&audio_format, 44100, SAMPLE_FORMAT_S16, 2);
- success = encoder_open(encoder, &audio_format, NULL);
- assert(success);
-
- encoder_to_stdout(encoder);
-
- /* write a block of data */
-
- success = encoder_write(encoder, zero, sizeof(zero), NULL);
- assert(success);
-
- encoder_to_stdout(encoder);
-
- /* write a tag */
-
- success = encoder_pre_tag(encoder, NULL);
- assert(success);
-
- encoder_to_stdout(encoder);
-
- struct tag *tag = tag_new();
- tag_add_item(tag, TAG_ARTIST, "Foo");
- tag_add_item(tag, TAG_TITLE, "Bar");
-
- success = encoder_tag(encoder, tag, NULL);
- assert(success);
-
- tag_free(tag);
-
- encoder_to_stdout(encoder);
-
- /* write another block of data */
-
- success = encoder_write(encoder, zero, sizeof(zero), NULL);
- assert(success);
-
- /* finish */
-
- success = encoder_end(encoder, NULL);
- assert(success);
-
- encoder_to_stdout(encoder);
-
- encoder_close(encoder);
- encoder_finish(encoder);
-}
diff --git a/test/test_vorbis_encoder.cxx b/test/test_vorbis_encoder.cxx
new file mode 100644
index 000000000..75e1462dc
--- /dev/null
+++ b/test/test_vorbis_encoder.cxx
@@ -0,0 +1,107 @@
+/*
+ * 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 "EncoderList.hxx"
+#include "EncoderPlugin.hxx"
+#include "AudioFormat.hxx"
+#include "conf.h"
+#include "stdbin.h"
+#include "Tag.hxx"
+
+#include <glib.h>
+
+#include <stddef.h>
+#include <unistd.h>
+
+static uint8_t zero[256];
+
+static void
+encoder_to_stdout(Encoder &encoder)
+{
+ size_t length;
+ static char buffer[32768];
+
+ while ((length = encoder_read(&encoder, buffer, sizeof(buffer))) > 0) {
+ G_GNUC_UNUSED ssize_t ignored = write(1, buffer, length);
+ }
+}
+
+int
+main(G_GNUC_UNUSED int argc, G_GNUC_UNUSED char **argv)
+{
+ G_GNUC_UNUSED bool success;
+
+ /* create the encoder */
+
+ const auto plugin = encoder_plugin_get("vorbis");
+ assert(plugin != NULL);
+
+ config_param param;
+ param.AddBlockParam("quality", "5.0", -1);
+
+ const auto encoder = encoder_init(*plugin, param, NULL);
+ assert(encoder != NULL);
+
+ /* open the encoder */
+
+ AudioFormat audio_format(44100, SampleFormat::S16, 2);
+ success = encoder_open(encoder, audio_format, NULL);
+ assert(success);
+
+ encoder_to_stdout(*encoder);
+
+ /* write a block of data */
+
+ success = encoder_write(encoder, zero, sizeof(zero), NULL);
+ assert(success);
+
+ encoder_to_stdout(*encoder);
+
+ /* write a tag */
+
+ success = encoder_pre_tag(encoder, NULL);
+ assert(success);
+
+ encoder_to_stdout(*encoder);
+
+ Tag tag;
+ tag.AddItem(TAG_ARTIST, "Foo");
+ tag.AddItem(TAG_TITLE, "Bar");
+
+ success = encoder_tag(encoder, &tag, NULL);
+ assert(success);
+
+ encoder_to_stdout(*encoder);
+
+ /* write another block of data */
+
+ success = encoder_write(encoder, zero, sizeof(zero), NULL);
+ assert(success);
+
+ /* finish */
+
+ success = encoder_end(encoder, NULL);
+ assert(success);
+
+ encoder_to_stdout(*encoder);
+
+ encoder_close(encoder);
+ encoder_finish(encoder);
+}
diff --git a/test/visit_archive.cxx b/test/visit_archive.cxx
new file mode 100644
index 000000000..047fe62c0
--- /dev/null
+++ b/test/visit_archive.cxx
@@ -0,0 +1,126 @@
+/*
+ * 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 "stdbin.h"
+#include "Tag.hxx"
+#include "conf.h"
+#include "IOThread.hxx"
+#include "InputInit.hxx"
+#include "ArchiveList.hxx"
+#include "ArchivePlugin.hxx"
+#include "ArchiveFile.hxx"
+#include "ArchiveVisitor.hxx"
+#include "fs/Path.hxx"
+
+#include <glib.h>
+
+#include <unistd.h>
+#include <stdlib.h>
+
+static void
+my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level,
+ const gchar *message, G_GNUC_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 {
+ printf("%s\n", path_utf8);
+ }
+};
+
+int
+main(int argc, char **argv)
+{
+ GError *error = nullptr;
+
+ if (argc != 3) {
+ fprintf(stderr, "Usage: visit_archive PLUGIN PATH\n");
+ return EXIT_FAILURE;
+ }
+
+ const char *plugin_name = argv[1];
+ const Path path = Path::FromFS(argv[2]);
+
+ /* initialize GLib */
+
+#if !GLIB_CHECK_VERSION(2,32,0)
+ g_thread_init(NULL);
+#endif
+
+ g_log_set_default_handler(my_log_func, NULL);
+
+ /* initialize MPD */
+
+ config_global_init();
+
+ io_thread_init();
+ if (!io_thread_start(&error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
+ archive_plugin_init_all();
+
+ if (!input_stream_global_init(&error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return 2;
+ }
+
+ /* open the archive and dump it */
+
+ const archive_plugin *plugin = archive_plugin_from_name(plugin_name);
+ if (plugin == nullptr) {
+ fprintf(stderr, "No such plugin: %s\n", plugin_name);
+ return EXIT_FAILURE;
+ }
+
+ int result = EXIT_SUCCESS;
+
+ ArchiveFile *file = archive_file_open(plugin, path.c_str(), &error);
+ if (file != nullptr) {
+ MyArchiveVisitor visitor;
+ file->Visit(visitor);
+ file->Close();
+ } else {
+ fprintf(stderr, "%s\n", error->message);
+ g_error_free(error);
+ result = EXIT_FAILURE;
+ }
+
+ /* deinitialize everything */
+
+ input_stream_global_finish();
+
+ archive_plugin_deinit_all();
+
+ io_thread_deinit();
+
+ config_global_finish();
+
+ return result;
+}
diff --git a/valgrind.suppressions b/valgrind.suppressions
index 2cce34186..3871d275b 100644
--- a/valgrind.suppressions
+++ b/valgrind.suppressions
@@ -10,6 +10,15 @@
...
fun:g_random_int
}
+{
+ <insert_a_suppression_name_here>
+ Memcheck:Leak
+ fun:*alloc
+ fun:g_mutex_impl_new
+ fun:g_mutex_get_impl
+ fun:g_mutex_lock
+ fun:g_main_context_new
+}
{
g_main_context_dispatch
@@ -98,7 +107,7 @@
}
{
- g_static_private_set
+ <insert_a_suppression_name_here>
Memcheck:Leak
fun:*alloc
...
@@ -106,6 +115,14 @@
}
{
+ <insert_a_suppression_name_here>
+ Memcheck:Leak
+ fun:*alloc
+ ...
+ fun:g_intern_string
+}
+
+{
g_get_language_names
Memcheck:Leak
fun:*alloc
@@ -309,6 +326,29 @@
}
{
+ <insert_a_suppression_name_here>
+ Memcheck:Leak
+ fun:malloc
+ fun:strdup
+ ...
+ fun:ao_initialize
+}
+
+{
+ <insert_a_suppression_name_here>
+ Memcheck:Leak
+ fun:calloc
+ fun:ao_initialize
+}
+
+{
+ <insert_a_suppression_name_here>
+ Memcheck:Addr4
+ ...
+ fun:WildMidi_Init
+}
+
+{
g_quark_from_string
Memcheck:Leak
fun:*alloc
@@ -354,54 +394,6 @@
Memcheck:Leak
fun:*alloc
...
- fun:g_type_init_with_debug_flags
-}
-
-{
- <insert_a_suppression_name_here>
- Memcheck:Leak
- fun:*alloc
- ...
- fun:g_type_register_static
-}
-
-{
- <insert_a_suppression_name_here>
- Memcheck:Leak
- fun:*alloc
- ...
- fun:g_type_add_interface_static
-}
-
-{
- <insert_a_suppression_name_here>
- Memcheck:Leak
- fun:*alloc
- ...
- fun:g_type_add_interface_check
-}
-
-{
- <insert_a_suppression_name_here>
- Memcheck:Leak
- fun:*alloc
- ...
- fun:g_type_interface_add_prerequisite
-}
-
-{
- <insert_a_suppression_name_here>
- Memcheck:Leak
- fun:calloc
- fun:g_malloc0
- fun:g_type_class_ref
-}
-
-{
- <insert_a_suppression_name_here>
- Memcheck:Leak
- fun:*alloc
- ...
fun:g_*_class_intern_init
}
@@ -442,38 +434,6 @@
Memcheck:Leak
fun:*alloc
...
- fun:soup_*_class_intern_init
-}
-
-{
- <insert_a_suppression_name_here>
- Memcheck:Leak
- fun:*alloc
- ...
- fun:soup_auth_manager_add_type
-}
-
-{
- <insert_a_suppression_name_here>
- Memcheck:Leak
- fun:*alloc
- ...
- fun:soup_auth_manager_class_intern_init
-}
-
-{
- <insert_a_suppression_name_here>
- Memcheck:Leak
- fun:*alloc
- ...
- fun:soup_auth_manager_ntlm_class_intern_init
-}
-
-{
- <insert_a_suppression_name_here>
- Memcheck:Leak
- fun:*alloc
- ...
fun:intern_header_name
}
@@ -509,14 +469,3 @@
fun:call_init
fun:_dl_init
}
-
-{
- <insert_a_suppression_name_here>
- Memcheck:Leak
- fun:*alloc
- fun:_dl_allocate_tls
- ...
- obj:*/libffado.so*
- fun:call_init
- fun:_dl_init
-}